From 8dce62a91aad6dabc582d56b7d89ff6dfc741dc0 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Tue, 29 Aug 2023 13:40:56 -0400 Subject: [PATCH 01/19] AMQP Automatic Instrumentation Automatically instrument and collect distributed traces on AMQP/RabbitMQ code with the New Relic Go agent. --- .github/workflows/ci.yaml | 2 + .../nramqp/examples/consumer/main.go | 78 +++++ .../nramqp/examples/publisher/main.go | 124 ++++++++ v3/integrations/nramqp/go.mod | 23 ++ v3/integrations/nramqp/headers.go | 69 ++++ v3/integrations/nramqp/headers_test.go | 296 ++++++++++++++++++ v3/integrations/nramqp/nramqp.go | 104 ++++++ v3/integrations/nramqp/nramqp_test.go | 78 +++++ v3/newrelic/attributes.go | 2 + v3/newrelic/attributes_from_internal.go | 1 + v3/newrelic/transaction.go | 6 + 11 files changed, 783 insertions(+) create mode 100644 v3/integrations/nramqp/examples/consumer/main.go create mode 100644 v3/integrations/nramqp/examples/publisher/main.go create mode 100644 v3/integrations/nramqp/go.mod create mode 100644 v3/integrations/nramqp/headers.go create mode 100644 v3/integrations/nramqp/headers_test.go create mode 100644 v3/integrations/nramqp/nramqp.go create mode 100644 v3/integrations/nramqp/nramqp_test.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2e3b02c6d..a21f108b9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,6 +25,8 @@ jobs: dirs: v3/newrelic,v3/internal,v3/examples # v3 integrations + - go-version: 1.19.x + dirs: v3/integrations/nramqp - go-version: 1.19.x dirs: v3/integrations/nrsarama - go-version: 1.19.x diff --git a/v3/integrations/nramqp/examples/consumer/main.go b/v3/integrations/nramqp/examples/consumer/main.go new file mode 100644 index 000000000..5cfc92ec4 --- /dev/null +++ b/v3/integrations/nramqp/examples/consumer/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/newrelic/go-agent/v3/integrations/nramqp" + "github.com/newrelic/go-agent/v3/newrelic" + + amqp "github.com/rabbitmq/amqp091-go" +) + +func failOnError(err error, msg string) { + if err != nil { + panic(fmt.Sprintf("%s: %s\n", msg, err)) + } +} + +// a rabit mq server must be running on localhost on port 5672 +func main() { + nrApp, err := newrelic.NewApplication( + newrelic.ConfigAppName("AMQP Consumer Example App"), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")), + newrelic.ConfigInfoLogger(os.Stdout), + ) + + if err != nil { + panic(err) + } + + nrApp.WaitForConnection(time.Second * 5) + + conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + failOnError(err, "Failed to connect to RabbitMQ") + defer conn.Close() + + ch, err := conn.Channel() + failOnError(err, "Failed to open a channel") + defer ch.Close() + + q, err := ch.QueueDeclare( + "hello", // name + false, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + failOnError(err, "Failed to declare a queue") + + var forever chan struct{} + + handleDelivery, msgs, err := nramqp.Consume(nrApp, ch, + q.Name, + "", + true, // auto-ack + false, // exclusive + false, // no-local + false, // no-wait + nil, // args) + ) + failOnError(err, "Failed to register a consumer") + + go func() { + for d := range msgs { + txn := handleDelivery(d) + log.Printf("Received a message: %s\n", d.Body) + txn.End() + } + }() + + log.Printf(" [*] Waiting for messages. To exit press CTRL+C") + <-forever + + nrApp.Shutdown(time.Second * 10) +} diff --git a/v3/integrations/nramqp/examples/publisher/main.go b/v3/integrations/nramqp/examples/publisher/main.go new file mode 100644 index 000000000..445947a08 --- /dev/null +++ b/v3/integrations/nramqp/examples/publisher/main.go @@ -0,0 +1,124 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/newrelic/go-agent/v3/integrations/nramqp" + "github.com/newrelic/go-agent/v3/newrelic" + + amqp "github.com/rabbitmq/amqp091-go" +) + +var indexHTML = ` + + + + +

Send a Rabbit MQ Message

+ +
+
+
+ +
+ + + + ` + +func failOnError(err error, msg string) { + if err != nil { + panic(fmt.Sprintf("%s: %s\n", msg, err)) + } +} + +type amqpServer struct { + ch *amqp.Channel + exchange string + routingKey string +} + +func NewServer(channel *amqp.Channel, exchangeName, routingKeyName string) *amqpServer { + return &amqpServer{ + channel, + exchangeName, + routingKeyName, + } +} + +func (serv *amqpServer) index(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, indexHTML) +} + +func (serv *amqpServer) publishPlainTxtMessage(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // get the message from the HTTP form + r.ParseForm() + message := r.Form.Get("msg") + + err := nramqp.PublishWithContext(serv.ch, + ctx, + serv.exchange, // exchange + serv.routingKey, // routing key + false, // mandatory + false, // immediate + amqp.Publishing{ + ContentType: "text/plain", + Body: []byte(message), + }) + + if err != nil { + txn := newrelic.FromContext(ctx) + txn.NoticeError(err) + } + + serv.index(w, r) +} + +// a rabit mq server must be running on localhost on port 5672 +func main() { + nrApp, err := newrelic.NewApplication( + newrelic.ConfigAppName("AMQP Publisher Example App"), + newrelic.ConfigFromEnvironment(), + newrelic.ConfigInfoLogger(os.Stdout), + ) + + if err != nil { + panic(err) + } + + nrApp.WaitForConnection(time.Second * 5) + + conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + failOnError(err, "Failed to connect to RabbitMQ") + defer conn.Close() + + ch, err := conn.Channel() + failOnError(err, "Failed to open a channel") + defer ch.Close() + + q, err := ch.QueueDeclare( + "hello", // name + false, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + failOnError(err, "Failed to declare a queue") + + server := NewServer(ch, "", q.Name) + + http.HandleFunc(newrelic.WrapHandleFunc(nrApp, "/", server.index)) + http.HandleFunc(newrelic.WrapHandleFunc(nrApp, "/message", server.publishPlainTxtMessage)) + + fmt.Println("\n\nlistening on: http://localhost:8000/") + http.ListenAndServe(":8000", nil) + + nrApp.Shutdown(time.Second * 10) +} diff --git a/v3/integrations/nramqp/go.mod b/v3/integrations/nramqp/go.mod new file mode 100644 index 000000000..0e8bf8c12 --- /dev/null +++ b/v3/integrations/nramqp/go.mod @@ -0,0 +1,23 @@ +module github.com/newrelic/go-agent/v3/integrations/nramqp + +go 1.19.0 + +require ( + github.com/newrelic/go-agent/v3 v3.27.0 + github.com/rabbitmq/amqp091-go v1.9.0 +) + +require ( + github.com/andybalholm/brotli v1.0.6 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/klauspost/compress v1.17.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.50.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) diff --git a/v3/integrations/nramqp/headers.go b/v3/integrations/nramqp/headers.go new file mode 100644 index 000000000..d3604e493 --- /dev/null +++ b/v3/integrations/nramqp/headers.go @@ -0,0 +1,69 @@ +package nramqp + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/newrelic/go-agent/v3/newrelic" + amqp "github.com/rabbitmq/amqp091-go" +) + +const ( + MaxHeaderLen = 4096 +) + +// Adds Distributed Tracing headers to the amqp table object +func injectDtHeaders(txn *newrelic.Transaction, headers amqp.Table) amqp.Table { + dummyHeaders := http.Header{} + + txn.InsertDistributedTraceHeaders(dummyHeaders) + if headers == nil { + headers = amqp.Table{} + } + + dtHeaders := dummyHeaders.Get(newrelic.DistributedTraceNewRelicHeader) + if dtHeaders != "" { + headers[newrelic.DistributedTraceNewRelicHeader] = dtHeaders + } + traceParent := dummyHeaders.Get(newrelic.DistributedTraceW3CTraceParentHeader) + if traceParent != "" { + headers[newrelic.DistributedTraceW3CTraceParentHeader] = traceParent + } + traceState := dummyHeaders.Get(newrelic.DistributedTraceW3CTraceStateHeader) + if traceState != "" { + headers[newrelic.DistributedTraceW3CTraceStateHeader] = traceState + } + + return headers +} + +func toHeader(headers amqp.Table) http.Header { + headersHTTP := http.Header{} + if headers == nil { + return headersHTTP + } + + for k, v := range headers { + headersHTTP.Set(k, fmt.Sprintf("%v", v)) + } + + return headersHTTP +} + +func getHeadersAttributeString(hdrs amqp.Table) (string, error) { + if len(hdrs) == 0 { + return "", nil + } + + delete(hdrs, newrelic.DistributedTraceNewRelicHeader) + delete(hdrs, newrelic.DistributedTraceW3CTraceParentHeader) + delete(hdrs, newrelic.DistributedTraceW3CTraceStateHeader) + + if len(hdrs) == 0 { + return "", nil + } + + bytes, err := json.Marshal(hdrs) + return string(bytes), err +} diff --git a/v3/integrations/nramqp/headers_test.go b/v3/integrations/nramqp/headers_test.go new file mode 100644 index 000000000..b5ee3548b --- /dev/null +++ b/v3/integrations/nramqp/headers_test.go @@ -0,0 +1,296 @@ +package nramqp + +import ( + "encoding/json" + "testing" + "time" + + "github.com/newrelic/go-agent/v3/internal" + "github.com/newrelic/go-agent/v3/internal/integrationsupport" + "github.com/newrelic/go-agent/v3/newrelic" + + amqp "github.com/rabbitmq/amqp091-go" +) + +var replyFn = func(reply *internal.ConnectReply) { + reply.SetSampleEverything() + reply.AccountID = "123" + reply.TrustedAccountKey = "123" + reply.PrimaryAppID = "456" +} + +var cfgFn = func(cfg *newrelic.Config) { + cfg.Attributes.Include = append(cfg.Attributes.Include, + newrelic.AttributeMessageRoutingKey, + newrelic.AttributeMessageQueueName, + newrelic.AttributeMessageExchangeType, + newrelic.AttributeMessageReplyTo, + newrelic.AttributeMessageCorrelationID, + newrelic.AttributeMessageHeaders, + ) +} + +func createTestApp() integrationsupport.ExpectApp { + return integrationsupport.NewTestApp(replyFn, cfgFn, integrationsupport.ConfigFullTraces, newrelic.ConfigCodeLevelMetricsEnabled(false)) +} + +func TestAddHeaderAttribute(t *testing.T) { + app := createTestApp() + txn := app.StartTransaction("test") + + hdrs := amqp.Table{ + "str": "hello", + "int": 5, + "bool": true, + "nil": nil, + "time": time.Now(), + "bytes": []byte("a slice of bytes"), + "decimal": amqp.Decimal{Scale: 2, Value: 12345}, + "zero decimal": amqp.Decimal{Scale: 0, Value: 12345}, + } + attrStr, err := getHeadersAttributeString(hdrs) + if err != nil { + t.Fatal(err) + } + integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageHeaders, attrStr, hdrs) + + txn.End() + + app.ExpectTxnTraces(t, []internal.WantTxnTrace{ + { + AgentAttributes: map[string]interface{}{ + newrelic.AttributeMessageHeaders: attrStr, + }, + }, + }) +} + +func TestInjectHeaders(t *testing.T) { + nrApp := createTestApp() + txn := nrApp.StartTransaction("test txn") + defer txn.End() + + msg := amqp.Publishing{} + msg.Headers = injectDtHeaders(txn, msg.Headers) + + if len(msg.Headers) != 3 { + t.Error("Expected DT headers to be injected into Headers object") + } +} + +func TestInjectHeadersPreservesExistingHeaders(t *testing.T) { + nrApp := createTestApp() + txn := nrApp.StartTransaction("test txn") + defer txn.End() + + msg := amqp.Publishing{ + Headers: amqp.Table{ + "one": 1, + "two": 2, + }, + } + msg.Headers = injectDtHeaders(txn, msg.Headers) + + if len(msg.Headers) != 5 { + t.Error("Expected DT headers to be injected into Headers object") + } +} + +func TestToHeader(t *testing.T) { + nrApp := createTestApp() + txn := nrApp.StartTransaction("test txn") + defer txn.End() + + msg := amqp.Publishing{ + Headers: amqp.Table{ + "one": 1, + "two": 2, + }, + } + msg.Headers = injectDtHeaders(txn, msg.Headers) + + hdr := toHeader(msg.Headers) + + if v := hdr.Get(newrelic.DistributedTraceNewRelicHeader); v == "" { + t.Errorf("header did not contain a DT header with the key %s", newrelic.DistributedTraceNewRelicHeader) + } + if v := hdr.Get(newrelic.DistributedTraceW3CTraceParentHeader); v == "" { + t.Errorf("header did not contain a DT header with the key %s", newrelic.DistributedTraceW3CTraceParentHeader) + } + if v := hdr.Get(newrelic.DistributedTraceW3CTraceStateHeader); v == "" { + t.Errorf("header did not contain a DT header with the key %s", newrelic.DistributedTraceW3CTraceStateHeader) + } +} + +func BenchmarkGetAttributeHeaders(b *testing.B) { + hdrs := amqp.Table{ + "str": "hello", + "int": 5, + "bool": true, + "nil": nil, + "time": time.Now(), + "bytes": []byte("a slice of bytes"), + "decimal": amqp.Decimal{Scale: 2, Value: 12345}, + "zero decimal": amqp.Decimal{Scale: 0, Value: 12345}, + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + getHeadersAttributeString(hdrs) + } +} + +func TestGetAttributeHeaders(t *testing.T) { + ti := time.Now() + hdrs := amqp.Table{ + "str": "hello", + "int": 5, + "bool": true, + "nil": nil, + "time": ti, + "bytes": []byte("a slice of bytes"), + "decimal": amqp.Decimal{Scale: 2, Value: 12345}, + "zero decimal": amqp.Decimal{Scale: 0, Value: 12345}, + "array": []interface{}{5, true, "hi", ti}, + } + + hdrStr, err := getHeadersAttributeString(hdrs) + if err != nil { + t.Fatal(err) + } + + t.Log(hdrStr) + + var v map[string]any + err = json.Unmarshal([]byte(hdrStr), &v) + if err != nil { + t.Fatal(err) + } + + if len(v) != 9 { + t.Errorf("expected 6 key value pairs, but got %d", len(v)) + } + + _, ok := v["str"] + if !ok { + t.Error("string header key value pair was dropped") + } + + _, ok = v["bytes"] + if !ok { + t.Error("bytes header key value pair was dropped") + } + + _, ok = v["int"] + if !ok { + t.Error("int header key value pair was dropped") + } + + _, ok = v["bool"] + if !ok { + t.Error("bool header key value pair was dropped") + } + + _, ok = v["nil"] + if !ok { + t.Error("nil header key value pair was dropped") + } + + _, ok = v["decimal"] + if !ok { + t.Error("decimal header key value pair was dropped") + } + + _, ok = v["zero decimal"] + if !ok { + t.Error("zero decimal header key value pair was dropped") + } + + _, ok = v["array"] + if !ok { + t.Error("array header key value pair was dropped") + } + + _, ok = v["time"] + if !ok { + t.Error("time header key value pair was dropped") + } +} + +func TestGetAttributeHeadersEmpty(t *testing.T) { + hdrs := amqp.Table{} + + hdrStr, err := getHeadersAttributeString(hdrs) + if err != nil { + t.Fatal(err) + } + if hdrStr != "" { + t.Errorf("should return empty string for empty or nil header table, instead got: %s", hdrStr) + } +} + +func TestGetAttributeHeadersNil(t *testing.T) { + hdrStr, err := getHeadersAttributeString(nil) + if err != nil { + t.Fatal(err) + } + if hdrStr != "" { + t.Errorf("should return empty string for empty or nil header table, instead got: %s", hdrStr) + } +} + +func TestGetAttributeHeadersIgnoresDT(t *testing.T) { + app := createTestApp() + txn := app.StartTransaction("test") + defer txn.End() + + hdrs := amqp.Table{ + "str": "hello", + } + + injectDtHeaders(txn, hdrs) + + hdrStr, err := getHeadersAttributeString(hdrs) + if err != nil { + t.Fatal(err) + } + t.Log(hdrStr) + + var v map[string]any + err = json.Unmarshal([]byte(hdrStr), &v) + if err != nil { + t.Fatal(err) + } + + if len(v) != 1 { + t.Errorf("expected 1 key value pair, but got %d", len(v)) + } + + val, ok := v["str"] + if !ok { + t.Error("string header key value pair was dropped") + } else if val.(string) != "hello" { + t.Error("string header value was corrupted") + } +} + +func TestGetAttributeHeadersEmptyAfterStrippingDT(t *testing.T) { + app := createTestApp() + txn := app.StartTransaction("test") + defer txn.End() + + hdrs := amqp.Table{} + + injectDtHeaders(txn, hdrs) + + hdrStr, err := getHeadersAttributeString(hdrs) + if err != nil { + t.Fatal(err) + } + + if hdrStr != "" { + t.Errorf("expected an empty header string, but got: %s", hdrStr) + } +} diff --git a/v3/integrations/nramqp/nramqp.go b/v3/integrations/nramqp/nramqp.go new file mode 100644 index 000000000..7fd3ab258 --- /dev/null +++ b/v3/integrations/nramqp/nramqp.go @@ -0,0 +1,104 @@ +package nramqp + +import ( + "context" + + amqp "github.com/rabbitmq/amqp091-go" + + "github.com/newrelic/go-agent/v3/internal" + "github.com/newrelic/go-agent/v3/internal/integrationsupport" + "github.com/newrelic/go-agent/v3/newrelic" +) + +const ( + RabbitMQLibrary = "RabbitMQ" +) + +func creatProducerSegment(exchange, key string) *newrelic.MessageProducerSegment { + s := newrelic.MessageProducerSegment{ + Library: RabbitMQLibrary, + DestinationName: "Default", + DestinationType: newrelic.MessageQueue, + } + + if exchange != "" { + s.DestinationName = exchange + s.DestinationType = newrelic.MessageExchange + } else if key != "" { + s.DestinationName = key + } + + return &s +} + +// PublishedWithContext looks for a newrelic transaction in the context object, and if found, creates a message producer segment. +// It will also inject distributed tracing headers into the message. +func PublishWithContext(ch *amqp.Channel, ctx context.Context, exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error { + txn := newrelic.FromContext(ctx) + if txn != nil { + // generate message broker segment + s := creatProducerSegment(exchange, key) + + // capture telemetry for AMQP producer + if msg.Headers != nil && len(msg.Headers) > 0 { + hdrStr, err := getHeadersAttributeString(msg.Headers) + if err != nil { + return err + } + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageHeaders, hdrStr) + } + + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageRoutingKey, key) + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageCorrelationID, msg.CorrelationId) + integrationsupport.AddAgentSpanAttribute(txn, newrelic.AttributeMessageReplyTo, msg.ReplyTo) + + // inject DT headers into headers object + msg.Headers = injectDtHeaders(txn, msg.Headers) + + s.StartTime = txn.StartSegmentNow() + err := ch.PublishWithContext(ctx, exchange, key, mandatory, immediate, msg) + s.End() + return err + } else { + return ch.PublishWithContext(ctx, exchange, key, mandatory, immediate, msg) + } +} + +// Consume performs a consume request on the provided amqp Channel, and returns a consume function, a consumer channel, and an error. +// The consumer function should be applied to each amqp Delivery that is read from the consume Channel, in order to collect tracing data +// on that message. The consume function will then return a transaction for that message. +func Consume(app *newrelic.Application, ch *amqp.Channel, queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args amqp.Table) (func(amqp.Delivery) *newrelic.Transaction, <-chan amqp.Delivery, error) { + var handler func(amqp.Delivery) *newrelic.Transaction + if app != nil { + handler = func(delivery amqp.Delivery) *newrelic.Transaction { + namer := internal.MessageMetricKey{ + Library: RabbitMQLibrary, + DestinationType: string(newrelic.MessageExchange), + DestinationName: queue, + Consumer: true, + } + + txn := app.StartTransaction(namer.Name()) + + hdrs := toHeader(delivery.Headers) + txn.AcceptDistributedTraceHeaders(newrelic.TransportAMQP, hdrs) + + if delivery.Headers != nil && len(delivery.Headers) > 0 { + hdrStr, err := getHeadersAttributeString(delivery.Headers) + if err == nil { + integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageHeaders, hdrStr, nil) + } + } + + integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageQueueName, queue, nil) + integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageRoutingKey, delivery.RoutingKey, nil) + integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageCorrelationID, delivery.CorrelationId, nil) + integrationsupport.AddAgentAttribute(txn, newrelic.AttributeMessageReplyTo, delivery.ReplyTo, nil) + + return txn + } + } + + msgChan, err := ch.Consume(queue, consumer, autoAck, exclusive, noLocal, noWait, args) + return handler, msgChan, err +} diff --git a/v3/integrations/nramqp/nramqp_test.go b/v3/integrations/nramqp/nramqp_test.go new file mode 100644 index 000000000..3db9e4ce9 --- /dev/null +++ b/v3/integrations/nramqp/nramqp_test.go @@ -0,0 +1,78 @@ +package nramqp + +import ( + "testing" + + "github.com/newrelic/go-agent/v3/newrelic" +) + +func BenchmarkCreateProducerSegment(b *testing.B) { + app := createTestApp() + txn := app.StartTransaction("test") + defer txn.End() + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + creatProducerSegment("exchange", "key") + } +} + +func TestCreateProducerSegment(t *testing.T) { + app := createTestApp() + txn := app.StartTransaction("test") + defer txn.End() + + type testObject struct { + exchange string + key string + expect newrelic.MessageProducerSegment + } + + tests := []testObject{ + { + "test exchange", + "", + newrelic.MessageProducerSegment{ + DestinationName: "test exchange", + DestinationType: newrelic.MessageExchange, + }, + }, + { + "", + "test queue", + newrelic.MessageProducerSegment{ + DestinationName: "test queue", + DestinationType: newrelic.MessageQueue, + }, + }, + { + "", + "", + newrelic.MessageProducerSegment{ + DestinationName: "Default", + DestinationType: newrelic.MessageQueue, + }, + }, + { + "test exchange", + "test queue", + newrelic.MessageProducerSegment{ + DestinationName: "test exchange", + DestinationType: newrelic.MessageExchange, + }, + }, + } + + for _, test := range tests { + s := creatProducerSegment(test.exchange, test.key) + if s.DestinationName != test.expect.DestinationName { + t.Errorf("expected destination name %s, got %s", test.expect.DestinationName, s.DestinationName) + } + if s.DestinationType != test.expect.DestinationType { + t.Errorf("expected destination type %s, got %s", test.expect.DestinationType, s.DestinationType) + } + } + +} diff --git a/v3/newrelic/attributes.go b/v3/newrelic/attributes.go index 258d23b4b..0221b5795 100644 --- a/v3/newrelic/attributes.go +++ b/v3/newrelic/attributes.go @@ -127,6 +127,8 @@ const ( AttributeMessageReplyTo = "message.replyTo" // The application-generated identifier used in RPC configurations. AttributeMessageCorrelationID = "message.correlationId" + // The headers of the message without CAT keys/values + AttributeMessageHeaders = "message.headers" ) // Attributes destined for Span Events. These attributes appear only on Span diff --git a/v3/newrelic/attributes_from_internal.go b/v3/newrelic/attributes_from_internal.go index b9a016300..64012b93b 100644 --- a/v3/newrelic/attributes_from_internal.go +++ b/v3/newrelic/attributes_from_internal.go @@ -49,6 +49,7 @@ var ( AttributeAWSLambdaEventSourceARN: usualDests, AttributeMessageRoutingKey: usualDests, AttributeMessageQueueName: usualDests, + AttributeMessageHeaders: usualDests, AttributeMessageExchangeType: destNone, AttributeMessageReplyTo: destNone, AttributeMessageCorrelationID: destNone, diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index d1d519c63..3f75945ed 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -352,6 +352,7 @@ func (txn *Transaction) InsertDistributedTraceHeaders(hdrs http.Header) { if txn == nil || txn.thread == nil { return } + txn.thread.CreateDistributedTracePayload(hdrs) } @@ -588,6 +589,11 @@ func (tt TransportType) toString() string { } } +const ( + RabbitMQMessageBrokerLibrary = "RabbitMQ" + ExchangeDestinationType = "Exchange" +) + // WebRequest is used to provide request information to Transaction.SetWebRequest. type WebRequest struct { // Header may be nil if you don't have any headers or don't want to From 678c58f267a44e30a9d6fbaff702a899c7cfe1bf Mon Sep 17 00:00:00 2001 From: Steve Willoughby Date: Tue, 17 Oct 2023 10:00:52 -0700 Subject: [PATCH 02/19] removed v2 code --- _integrations/README.md | 3 - _integrations/logcontext/README.md | 8 - _integrations/logcontext/logcontext.go | 41 - .../logcontext/nrlogrusplugin/README.md | 10 - .../logcontext/nrlogrusplugin/example/main.go | 73 - .../nrlogrusplugin/nrlogrusplugin.go | 189 -- .../nrlogrusplugin/nrlogrusplugin_test.go | 397 --- _integrations/nrawssdk/README.md | 3 - _integrations/nrawssdk/internal/internal.go | 102 - .../nrawssdk/internal/internal_test.go | 116 - _integrations/nrawssdk/v1/README.md | 10 - _integrations/nrawssdk/v1/nrawssdk.go | 87 - _integrations/nrawssdk/v1/nrawssdk_test.go | 589 ----- _integrations/nrawssdk/v2/README.md | 10 - _integrations/nrawssdk/v2/nrawssdk.go | 88 - _integrations/nrawssdk/v2/nrawssdk_test.go | 582 ----- _integrations/nrb3/README.md | 10 - _integrations/nrb3/example_test.go | 72 - _integrations/nrb3/nrb3.go | 82 - _integrations/nrb3/nrb3_doc.go | 13 - _integrations/nrb3/nrb3_test.go | 157 -- _integrations/nrecho/README.md | 10 - _integrations/nrecho/example/main.go | 61 - _integrations/nrecho/nrecho.go | 93 - _integrations/nrecho/nrecho_test.go | 250 -- _integrations/nrgin/v1/README.md | 10 - _integrations/nrgin/v1/example/main.go | 96 - _integrations/nrgin/v1/nrgin.go | 123 - _integrations/nrgin/v1/nrgin_context_test.go | 118 - _integrations/nrgin/v1/nrgin_test.go | 253 -- _integrations/nrgorilla/v1/README.md | 10 - _integrations/nrgorilla/v1/example/main.go | 53 - _integrations/nrgorilla/v1/nrgorilla.go | 77 - _integrations/nrgorilla/v1/nrgorilla_test.go | 130 - _integrations/nrgrpc/README.md | 10 - _integrations/nrgrpc/example/client/client.go | 134 - .../nrgrpc/example/sampleapp/sampleapp.pb.go | 369 --- .../nrgrpc/example/sampleapp/sampleapp.proto | 15 - _integrations/nrgrpc/example/server/server.go | 100 - _integrations/nrgrpc/nrgrpc_client.go | 135 - _integrations/nrgrpc/nrgrpc_client_test.go | 597 ----- _integrations/nrgrpc/nrgrpc_doc.go | 47 - _integrations/nrgrpc/nrgrpc_server.go | 136 - _integrations/nrgrpc/nrgrpc_server_test.go | 607 ----- _integrations/nrgrpc/testapp/README.md | 17 - _integrations/nrgrpc/testapp/server.go | 84 - _integrations/nrgrpc/testapp/testapp.pb.go | 469 ---- _integrations/nrgrpc/testapp/testapp.proto | 18 - _integrations/nrhttprouter/README.md | 10 - _integrations/nrhttprouter/example/main.go | 47 - _integrations/nrhttprouter/nrhttprouter.go | 151 -- .../nrhttprouter/nrhttprouter_test.go | 286 --- _integrations/nrlambda/README.md | 10 - _integrations/nrlambda/config.go | 42 - _integrations/nrlambda/config_test.go | 41 - _integrations/nrlambda/events.go | 127 - _integrations/nrlambda/events_test.go | 219 -- _integrations/nrlambda/example/main.go | 41 - _integrations/nrlambda/handler.go | 162 -- _integrations/nrlambda/handler_test.go | 468 ---- _integrations/nrlogrus/README.md | 10 - _integrations/nrlogrus/example/main.go | 40 - _integrations/nrlogrus/nrlogrus.go | 70 - _integrations/nrlogrus/nrlogrus_test.go | 49 - _integrations/nrlogxi/v1/README.md | 10 - _integrations/nrlogxi/v1/example_test.go | 23 - _integrations/nrlogxi/v1/nrlogxi.go | 52 - _integrations/nrmicro/README.md | 10 - _integrations/nrmicro/example/README.md | 9 - .../nrmicro/example/client/client.go | 58 - .../nrmicro/example/proto/greeter.micro.go | 94 - .../nrmicro/example/proto/greeter.pb.go | 122 - .../nrmicro/example/proto/greeter.proto | 16 - _integrations/nrmicro/example/pubsub/main.go | 77 - .../nrmicro/example/server/server.go | 67 - _integrations/nrmicro/nrmicro.go | 238 -- _integrations/nrmicro/nrmicro_doc.go | 184 -- _integrations/nrmicro/nrmicro_test.go | 1034 -------- _integrations/nrmongo/README.md | 10 - _integrations/nrmongo/example/main.go | 51 - _integrations/nrmongo/nrmongo.go | 159 -- _integrations/nrmongo/nrmongo_test.go | 248 -- _integrations/nrmysql/README.md | 10 - _integrations/nrmysql/example/main.go | 51 - _integrations/nrmysql/nrmysql.go | 105 - _integrations/nrmysql/nrmysql_test.go | 185 -- _integrations/nrnats/README.md | 10 - _integrations/nrnats/example_test.go | 59 - _integrations/nrnats/examples/README.md | 5 - _integrations/nrnats/examples/main.go | 206 -- _integrations/nrnats/nrnats.go | 66 - _integrations/nrnats/nrnats_doc.go | 55 - _integrations/nrnats/nrnats_test.go | 229 -- _integrations/nrpkgerrors/README.md | 10 - _integrations/nrpkgerrors/example/main.go | 60 - _integrations/nrpkgerrors/example_test.go | 30 - _integrations/nrpkgerrors/nrkpgerrors_test.go | 102 - _integrations/nrpkgerrors/nrpkgerrors.go | 84 - _integrations/nrpq/README.md | 10 - _integrations/nrpq/example/main.go | 49 - _integrations/nrpq/example/sqlx/main.go | 146 -- _integrations/nrpq/nrpq.go | 160 -- _integrations/nrpq/nrpq_test.go | 254 -- _integrations/nrsqlite3/README.md | 10 - _integrations/nrsqlite3/example/main.go | 52 - _integrations/nrsqlite3/nrsqlite3.go | 144 -- _integrations/nrsqlite3/nrsqlite3_test.go | 32 - _integrations/nrstan/README.md | 10 - _integrations/nrstan/examples/README.md | 4 - _integrations/nrstan/examples/main.go | 110 - _integrations/nrstan/nrstan.go | 36 - _integrations/nrstan/nrstan_doc.go | 48 - _integrations/nrstan/nrstan_test.go | 115 - _integrations/nrzap/README.md | 10 - _integrations/nrzap/example_test.go | 21 - _integrations/nrzap/nrzap.go | 45 - app_run.go | 212 -- app_run_test.go | 388 --- application.go | 70 - attributes.go | 119 - browser_header.go | 83 - browser_header_test.go | 68 - config.go | 416 --- context.go | 52 - context_stub.go | 17 - datastore.go | 35 - doc.go | 9 - errors.go | 62 - examples/README.md | 3 - examples/client-round-tripper/main.go | 70 - examples/client/main.go | 61 - examples/custom-instrumentation/main.go | 109 - examples/server-http/main.go | 84 - examples/server/main.go | 299 --- examples/short-lived-process/main.go | 49 - examples_test.go | 216 -- instrumentation.go | 116 - internal/adaptive_sampler.go | 102 - internal/adaptive_sampler_test.go | 96 - internal/analytics_events.go | 148 -- internal/analytics_events_test.go | 346 --- internal/apdex.go | 51 - internal/apdex_test.go | 49 - internal/attributes.go | 614 ----- internal/attributes_test.go | 461 ---- internal/browser.go | 21 - internal/browser_test.go | 29 - internal/cat/appdata.go | 114 - internal/cat/appdata_test.go | 123 - internal/cat/errors.go | 18 - internal/cat/headers.go | 16 - internal/cat/id.go | 44 - internal/cat/id_test.go | 56 - internal/cat/path_hash.go | 38 - internal/cat/path_hash_test.go | 35 - internal/cat/synthetics.go | 85 - internal/cat/synthetics_test.go | 120 - internal/cat/txndata.go | 99 - internal/cat/txndata_test.go | 143 -- internal/cat_test.go | 177 -- internal/collector.go | 336 --- internal/collector_test.go | 595 ----- internal/compress.go | 26 - internal/connect_reply.go | 240 -- internal/connect_reply_test.go | 224 -- internal/context.go | 20 - internal/cross_process_http.go | 70 - internal/cross_process_http_test.go | 186 -- internal/crossagent/README.md | 3 - .../crossagent/cross_agent_tests/README.md | 35 - .../attribute_configuration.json | 630 ----- .../cross_agent_tests/cat/README.md | 28 - .../cross_agent_tests/cat/cat_map.json | 595 ----- .../cross_agent_tests/cat/path_hashing.json | 51 - .../cross_agent_tests/collector_hostname.json | 71 - .../data_collection_server_configuration.json | 219 -- .../cross_agent_tests/datastores/README.md | 16 - .../datastores/datastore_api.json | 443 ---- .../datastores/datastore_instances.json | 73 - .../distributed_tracing.json | 1281 ---------- .../docker_container_id/README.md | 6 - .../docker_container_id/cases.json | 86 - .../docker_container_id/docker-0.9.1.txt | 10 - .../docker_container_id/docker-1.0.0.txt | 10 - .../docker-1.1.2-lxc-driver.txt | 10 - .../docker-1.1.2-native-driver-fs.txt | 10 - .../docker-1.1.2-native-driver-systemd.txt | 10 - .../docker_container_id/docker-1.3.txt | 9 - .../docker-custom-prefix.txt | 10 - .../docker_container_id/docker-too-long.txt | 10 - .../docker_container_id/empty.txt | 0 .../docker_container_id/heroku.txt | 1 - .../invalid-characters.txt | 9 - .../docker_container_id/invalid-length.txt | 9 - .../docker_container_id/no_cpu_subsystem.txt | 10 - .../ubuntu-14.04-lxc-container.txt | 10 - .../ubuntu-14.04-no-container.txt | 10 - .../ubuntu-14.10-no-container.txt | 10 - .../crossagent/cross_agent_tests/labels.json | 195 -- .../language_agents_security_policies.json | 269 -- .../postgres_explain_obfuscation/README.md | 16 - .../basic_where.colon_obfuscated.txt | 3 - .../basic_where.explain.txt | 3 - .../basic_where.obfuscated.txt | 3 - .../basic_where.query.txt | 1 - .../current_date.colon_obfuscated.txt | 2 - .../current_date.explain.txt | 2 - .../current_date.obfuscated.txt | 2 - .../current_date.query.txt | 1 - .../date.colon_obfuscated.txt | 2 - .../date.explain.txt | 2 - .../date.obfuscated.txt | 2 - .../date.query.txt | 1 - .../embedded_newline.colon_obfuscated.txt | 2 - .../embedded_newline.explain.txt | 3 - .../embedded_newline.obfuscated.txt | 2 - .../embedded_newline.query.txt | 1 - .../embedded_quote.colon_obfuscated.txt | 2 - .../embedded_quote.explain.txt | 2 - .../embedded_quote.obfuscated.txt | 2 - .../embedded_quote.query.txt | 1 - .../floating_point.colon_obfuscated.txt | 2 - .../floating_point.explain.txt | 2 - .../floating_point.obfuscated.txt | 2 - .../floating_point.query.txt | 1 - ...function_with_strings.colon_obfuscated.txt | 5 - .../function_with_strings.explain.txt | 5 - .../function_with_strings.obfuscated.txt | 5 - .../function_with_strings.query.txt | 1 - .../quote_in_table_name.colon_obfuscated.txt | 2 - .../quote_in_table_name.explain.txt | 2 - .../quote_in_table_name.obfuscated.txt | 2 - .../quote_in_table_name.query.txt | 1 - .../subplan.colon_obfuscated.txt | 5 - .../subplan.explain.txt | 5 - .../subplan.obfuscated.txt | 5 - .../subplan.query.txt | 1 - .../where_with_integer.colon_obfuscated.txt | 2 - .../where_with_integer.explain.txt | 2 - .../where_with_integer.obfuscated.txt | 2 - .../where_with_integer.query.txt | 1 - ...here_with_regex_chars.colon_obfuscated.txt | 2 - .../where_with_regex_chars.explain.txt | 2 - .../where_with_regex_chars.obfuscated.txt | 2 - .../where_with_regex_chars.query.txt | 1 - .../where_with_substring.colon_obfuscated.txt | 3 - .../where_with_substring.explain.txt | 3 - .../where_with_substring.obfuscated.txt | 3 - .../where_with_substring.query.txt | 1 - .../with_escape_case1.colon_obfuscated.txt | 2 - .../with_escape_case1.explain.txt | 2 - .../with_escape_case1.obfuscated.txt | 2 - .../with_escape_case1.query.txt | 1 - .../with_escape_case2.colon_obfuscated.txt | 2 - .../with_escape_case2.explain.txt | 3 - .../with_escape_case2.obfuscated.txt | 2 - .../with_escape_case2.query.txt | 1 - .../with_escape_case3.colon_obfuscated.txt | 2 - .../with_escape_case3.explain.txt | 2 - .../with_escape_case3.obfuscated.txt | 2 - .../with_escape_case3.query.txt | 1 - .../with_escape_case4.colon_obfuscated.txt | 2 - .../with_escape_case4.explain.txt | 2 - .../with_escape_case4.obfuscated.txt | 2 - .../with_escape_case4.query.txt | 1 - .../with_escape_case5.colon_obfuscated.txt | 2 - .../with_escape_case5.explain.txt | 2 - .../with_escape_case5.obfuscated.txt | 2 - .../with_escape_case5.query.txt | 1 - .../with_escape_case6.colon_obfuscated.txt | 2 - .../with_escape_case6.explain.txt | 2 - .../with_escape_case6.obfuscated.txt | 2 - .../with_escape_case6.query.txt | 1 - .../with_escape_case7.colon_obfuscated.txt | 2 - .../with_escape_case7.explain.txt | 2 - .../with_escape_case7.obfuscated.txt | 2 - .../with_escape_case7.query.txt | 1 - .../with_escape_case8.colon_obfuscated.txt | 2 - .../with_escape_case8.explain.txt | 2 - .../with_escape_case8.obfuscated.txt | 2 - .../with_escape_case8.query.txt | 1 - .../with_escape_case9.colon_obfuscated.txt | 2 - .../with_escape_case9.explain.txt | 2 - .../with_escape_case9.obfuscated.txt | 2 - .../with_escape_case9.query.txt | 1 - .../proc_cpuinfo/1pack_1core_1logical.txt | 3 - .../proc_cpuinfo/1pack_1core_2logical.txt | 14 - .../proc_cpuinfo/1pack_2core_2logical.txt | 14 - .../proc_cpuinfo/1pack_4core_4logical.txt | 28 - .../proc_cpuinfo/2pack_12core_24logical.txt | 575 ----- .../proc_cpuinfo/2pack_20core_40logical.txt | 999 -------- .../proc_cpuinfo/2pack_2core_2logical.txt | 51 - .../proc_cpuinfo/2pack_2core_4logical.txt | 28 - .../proc_cpuinfo/2pack_4core_4logical.txt | 28 - .../proc_cpuinfo/4pack_4core_4logical.txt | 103 - .../proc_cpuinfo/8pack_8core_8logical.txt | 199 -- .../cross_agent_tests/proc_cpuinfo/README.md | 28 - .../proc_cpuinfo/Xpack_Xcore_2logical.txt | 43 - .../proc_cpuinfo/malformed_file.txt | 3 - .../cross_agent_tests/proc_meminfo/README.md | 7 - .../proc_meminfo/meminfo_4096MB.txt | 47 - .../crossagent/cross_agent_tests/rules.json | 162 -- .../cross_agent_tests/rum_client_config.json | 91 - .../close-body-in-comment.html | 10 - .../dynamic-iframe.html | 19 - .../rum_loader_insertion_location/basic.html | 10 - .../body_with_attributes.html | 3 - .../charset_tag.html | 11 - .../charset_tag_after_x_ua_tag.html | 11 - .../charset_tag_before_x_ua_tag.html | 11 - .../charset_tag_with_spaces.html | 11 - .../comments1.html | 24 - .../comments2.html | 24 - .../content_type_charset_tag.html | 11 - ...ntent_type_charset_tag_after_x_ua_tag.html | 11 - ...tent_type_charset_tag_before_x_ua_tag.html | 11 - .../gt_in_quotes1.html | 27 - .../gt_in_quotes2.html | 24 - .../gt_in_quotes_mismatch.html | 24 - .../gt_in_single_quotes1.html | 25 - .../gt_in_single_quotes_mismatch.html | 25 - .../head_with_attributes.html | 10 - .../incomplete_non_meta_tags.html | 10 - .../no_end_header.html | 6 - .../no_header.html | 7 - .../no_html_and_no_header.html | 3 - .../no_start_header.html | 9 - .../script1.html | 19 - .../script2.html | 17 - .../x_ua_meta_tag.html | 10 - .../x_ua_meta_tag_multiline.html | 11 - .../x_ua_meta_tag_multiple_tags.html | 12 - .../x_ua_meta_tag_spaces_around_equals.html | 10 - .../x_ua_meta_tag_with_others.html | 11 - .../x_ua_meta_tag_with_spaces.html | 10 - .../sql_obfuscation/README.md | 36 - .../sql_obfuscation/sql_obfuscation.json | 650 ----- .../cross_agent_tests/sql_parsing.json | 67 - .../cross_agent_tests/synthetics/README.md | 65 - .../synthetics/synthetics.json | 317 --- .../transaction_segment_terms.json | 389 --- .../cross_agent_tests/url_clean.json | 15 - .../url_domain_extraction.json | 35 - .../cross_agent_tests/utilization/README.md | 36 - .../utilization/boot_id.json | 109 - .../utilization/utilization_json.json | 375 --- .../utilization_vendor_specific/README.md | 10 - .../utilization_vendor_specific/aws.json | 233 -- .../utilization_vendor_specific/azure.json | 287 --- .../utilization_vendor_specific/gcp.json | 288 --- .../utilization_vendor_specific/pcf.json | 281 --- internal/crossagent/crossagent.go | 57 - internal/custom_event.go | 106 - internal/custom_event_test.go | 226 -- internal/custom_events.go | 36 - internal/custom_metric.go | 15 - internal/distributed_tracing.go | 206 -- internal/distributed_tracing_test.go | 335 --- internal/environment.go | 64 - internal/environment_test.go | 45 - internal/error_events.go | 67 - internal/error_events_test.go | 230 -- internal/errors.go | 178 -- internal/errors_test.go | 355 --- internal/expect.go | 598 ----- internal/harvest.go | 403 --- internal/harvest_test.go | 878 ------- .../integrationsupport/integrationsupport.go | 82 - internal/intrinsics.go | 44 - internal/json_object_writer.go | 64 - internal/jsonx/encode.go | 174 -- internal/jsonx/encode_test.go | 182 -- internal/labels.go | 26 - internal/limits.go | 78 - internal/logger/logger.go | 96 - internal/metric_names.go | 305 --- internal/metric_rules.go | 167 -- internal/metric_rules_test.go | 99 - internal/metrics.go | 264 -- internal/metrics_test.go | 338 --- internal/obfuscate.go | 42 - internal/obfuscate_test.go | 82 - internal/priority.go | 30 - internal/priority_test.go | 26 - internal/queuing.go | 75 - internal/queuing_test.go | 96 - internal/rand.go | 62 - internal/rules_cache.go | 55 - internal/rules_cache_test.go | 59 - internal/sampler.go | 148 -- internal/sampler_test.go | 88 - internal/security_policies.go | 114 - internal/security_policies_test.go | 112 - internal/segment_terms.go | 148 -- internal/segment_terms_test.go | 137 - internal/serverless.go | 220 -- internal/serverless_test.go | 113 - internal/slow_queries.go | 264 -- internal/slow_queries_test.go | 290 --- internal/span_events.go | 146 -- internal/span_events_test.go | 262 -- internal/sqlparse/sqlparse.go | 63 - internal/sqlparse/sqlparse_test.go | 194 -- internal/stack_frame.go | 27 - internal/stack_frame_pre_1_7.go | 37 - internal/stacktrace.go | 98 - internal/stacktrace_test.go | 151 -- internal/stacktracetest/stacktracetest.go | 20 - internal/synthetics_test.go | 237 -- internal/sysinfo/bootid.go | 53 - internal/sysinfo/docker.go | 117 - internal/sysinfo/docker_test.go | 54 - internal/sysinfo/errors.go | 13 - internal/sysinfo/hostname_generic.go | 13 - internal/sysinfo/hostname_linux.go | 53 - internal/sysinfo/memtotal.go | 43 - internal/sysinfo/memtotal_darwin.go | 32 - internal/sysinfo/memtotal_darwin_test.go | 49 - internal/sysinfo/memtotal_freebsd.go | 35 - internal/sysinfo/memtotal_freebsd_test.go | 49 - internal/sysinfo/memtotal_linux.go | 17 - internal/sysinfo/memtotal_openbsd_amd64.go | 35 - internal/sysinfo/memtotal_openbsd_test.go | 49 - internal/sysinfo/memtotal_solaris.go | 29 - internal/sysinfo/memtotal_solaris_test.go | 62 - internal/sysinfo/memtotal_test.go | 56 - internal/sysinfo/memtotal_windows.go | 26 - internal/sysinfo/usage.go | 14 - internal/sysinfo/usage_posix.go | 29 - internal/sysinfo/usage_windows.go | 37 - .../tools/interface-wrapping/driver_conn.json | 18 - .../interface-wrapping/driver_driver.json | 11 - .../tools/interface-wrapping/driver_stmt.json | 14 - internal/tools/interface-wrapping/main.go | 112 - .../transaction_response_writer.json | 14 - internal/tools/rules/main.go | 50 - internal/tools/uncompress-serverless/main.go | 28 - internal/trace_id_generator.go | 34 - internal/trace_id_generator_test.go | 14 - internal/tracing.go | 771 ------ internal/tracing_test.go | 842 ------- internal/txn_cross_process.go | 420 --- internal/txn_cross_process_test.go | 822 ------ internal/txn_events.go | 197 -- internal/txn_events_test.go | 353 --- internal/txn_trace.go | 453 ---- internal/txn_trace_test.go | 1286 ---------- internal/url.go | 46 - internal/url_test.go | 88 - internal/utilities.go | 120 - internal/utilities_test.go | 164 -- internal/utilization/addresses.go | 76 - internal/utilization/aws.go | 92 - internal/utilization/aws_test.go | 53 - internal/utilization/azure.go | 105 - internal/utilization/azure_test.go | 57 - internal/utilization/fqdn.go | 31 - internal/utilization/fqdn_pre18.go | 14 - internal/utilization/gcp.go | 155 -- internal/utilization/gcp_test.go | 77 - internal/utilization/pcf.go | 83 - internal/utilization/pcf_test.go | 51 - internal/utilization/provider.go | 63 - internal/utilization/provider_test.go | 144 -- internal/utilization/utilization.go | 242 -- internal/utilization/utilization_test.go | 328 --- internal_app.go | 574 ----- internal_app_test.go | 26 - internal_attributes_test.go | 650 ----- internal_benchmark_test.go | 225 -- internal_browser_test.go | 179 -- internal_config.go | 193 -- internal_config_test.go | 478 ---- internal_context_test.go | 95 - internal_cross_process_test.go | 244 -- internal_distributed_trace_test.go | 1635 ------------ internal_errors_13_test.go | 54 - internal_errors_stacktrace_test.go | 79 - internal_errors_test.go | 669 ----- internal_response_writer.go | 157 -- internal_response_writer_test.go | 109 - internal_segment_attributes_test.go | 509 ---- internal_serverless_test.go | 304 --- internal_set_web_request_test.go | 325 --- internal_set_web_response_test.go | 105 - internal_slow_queries_test.go | 851 ------- internal_span_events_test.go | 591 ----- internal_synthetics_test.go | 195 -- internal_test.go | 2179 ---------------- internal_txn.go | 1246 --------- internal_txn_test.go | 556 ---- log.go | 35 - segments.go | 203 -- sql_driver.go | 271 -- sql_driver_optional_methods.go | 2243 ----------------- sql_driver_test.go | 187 -- transaction.go | 271 -- v3/integrations/nrlogrus/go.mod | 3 +- v3/integrations/nrmysql/go.mod | 1 + v3/integrations/nrstan/test/go.mod | 3 +- version.go | 24 - 501 files changed, 5 insertions(+), 63894 deletions(-) delete mode 100644 _integrations/README.md delete mode 100644 _integrations/logcontext/README.md delete mode 100644 _integrations/logcontext/logcontext.go delete mode 100644 _integrations/logcontext/nrlogrusplugin/README.md delete mode 100644 _integrations/logcontext/nrlogrusplugin/example/main.go delete mode 100644 _integrations/logcontext/nrlogrusplugin/nrlogrusplugin.go delete mode 100644 _integrations/logcontext/nrlogrusplugin/nrlogrusplugin_test.go delete mode 100644 _integrations/nrawssdk/README.md delete mode 100644 _integrations/nrawssdk/internal/internal.go delete mode 100644 _integrations/nrawssdk/internal/internal_test.go delete mode 100644 _integrations/nrawssdk/v1/README.md delete mode 100644 _integrations/nrawssdk/v1/nrawssdk.go delete mode 100644 _integrations/nrawssdk/v1/nrawssdk_test.go delete mode 100644 _integrations/nrawssdk/v2/README.md delete mode 100644 _integrations/nrawssdk/v2/nrawssdk.go delete mode 100644 _integrations/nrawssdk/v2/nrawssdk_test.go delete mode 100644 _integrations/nrb3/README.md delete mode 100644 _integrations/nrb3/example_test.go delete mode 100644 _integrations/nrb3/nrb3.go delete mode 100644 _integrations/nrb3/nrb3_doc.go delete mode 100644 _integrations/nrb3/nrb3_test.go delete mode 100644 _integrations/nrecho/README.md delete mode 100644 _integrations/nrecho/example/main.go delete mode 100644 _integrations/nrecho/nrecho.go delete mode 100644 _integrations/nrecho/nrecho_test.go delete mode 100644 _integrations/nrgin/v1/README.md delete mode 100644 _integrations/nrgin/v1/example/main.go delete mode 100644 _integrations/nrgin/v1/nrgin.go delete mode 100644 _integrations/nrgin/v1/nrgin_context_test.go delete mode 100644 _integrations/nrgin/v1/nrgin_test.go delete mode 100644 _integrations/nrgorilla/v1/README.md delete mode 100644 _integrations/nrgorilla/v1/example/main.go delete mode 100644 _integrations/nrgorilla/v1/nrgorilla.go delete mode 100644 _integrations/nrgorilla/v1/nrgorilla_test.go delete mode 100644 _integrations/nrgrpc/README.md delete mode 100644 _integrations/nrgrpc/example/client/client.go delete mode 100644 _integrations/nrgrpc/example/sampleapp/sampleapp.pb.go delete mode 100644 _integrations/nrgrpc/example/sampleapp/sampleapp.proto delete mode 100644 _integrations/nrgrpc/example/server/server.go delete mode 100644 _integrations/nrgrpc/nrgrpc_client.go delete mode 100644 _integrations/nrgrpc/nrgrpc_client_test.go delete mode 100644 _integrations/nrgrpc/nrgrpc_doc.go delete mode 100644 _integrations/nrgrpc/nrgrpc_server.go delete mode 100644 _integrations/nrgrpc/nrgrpc_server_test.go delete mode 100644 _integrations/nrgrpc/testapp/README.md delete mode 100644 _integrations/nrgrpc/testapp/server.go delete mode 100644 _integrations/nrgrpc/testapp/testapp.pb.go delete mode 100644 _integrations/nrgrpc/testapp/testapp.proto delete mode 100644 _integrations/nrhttprouter/README.md delete mode 100644 _integrations/nrhttprouter/example/main.go delete mode 100644 _integrations/nrhttprouter/nrhttprouter.go delete mode 100644 _integrations/nrhttprouter/nrhttprouter_test.go delete mode 100644 _integrations/nrlambda/README.md delete mode 100644 _integrations/nrlambda/config.go delete mode 100644 _integrations/nrlambda/config_test.go delete mode 100644 _integrations/nrlambda/events.go delete mode 100644 _integrations/nrlambda/events_test.go delete mode 100644 _integrations/nrlambda/example/main.go delete mode 100644 _integrations/nrlambda/handler.go delete mode 100644 _integrations/nrlambda/handler_test.go delete mode 100644 _integrations/nrlogrus/README.md delete mode 100644 _integrations/nrlogrus/example/main.go delete mode 100644 _integrations/nrlogrus/nrlogrus.go delete mode 100644 _integrations/nrlogrus/nrlogrus_test.go delete mode 100644 _integrations/nrlogxi/v1/README.md delete mode 100644 _integrations/nrlogxi/v1/example_test.go delete mode 100644 _integrations/nrlogxi/v1/nrlogxi.go delete mode 100644 _integrations/nrmicro/README.md delete mode 100644 _integrations/nrmicro/example/README.md delete mode 100644 _integrations/nrmicro/example/client/client.go delete mode 100644 _integrations/nrmicro/example/proto/greeter.micro.go delete mode 100644 _integrations/nrmicro/example/proto/greeter.pb.go delete mode 100644 _integrations/nrmicro/example/proto/greeter.proto delete mode 100644 _integrations/nrmicro/example/pubsub/main.go delete mode 100644 _integrations/nrmicro/example/server/server.go delete mode 100644 _integrations/nrmicro/nrmicro.go delete mode 100644 _integrations/nrmicro/nrmicro_doc.go delete mode 100644 _integrations/nrmicro/nrmicro_test.go delete mode 100644 _integrations/nrmongo/README.md delete mode 100644 _integrations/nrmongo/example/main.go delete mode 100644 _integrations/nrmongo/nrmongo.go delete mode 100644 _integrations/nrmongo/nrmongo_test.go delete mode 100644 _integrations/nrmysql/README.md delete mode 100644 _integrations/nrmysql/example/main.go delete mode 100644 _integrations/nrmysql/nrmysql.go delete mode 100644 _integrations/nrmysql/nrmysql_test.go delete mode 100644 _integrations/nrnats/README.md delete mode 100644 _integrations/nrnats/example_test.go delete mode 100644 _integrations/nrnats/examples/README.md delete mode 100644 _integrations/nrnats/examples/main.go delete mode 100644 _integrations/nrnats/nrnats.go delete mode 100644 _integrations/nrnats/nrnats_doc.go delete mode 100644 _integrations/nrnats/nrnats_test.go delete mode 100644 _integrations/nrpkgerrors/README.md delete mode 100644 _integrations/nrpkgerrors/example/main.go delete mode 100644 _integrations/nrpkgerrors/example_test.go delete mode 100644 _integrations/nrpkgerrors/nrkpgerrors_test.go delete mode 100644 _integrations/nrpkgerrors/nrpkgerrors.go delete mode 100644 _integrations/nrpq/README.md delete mode 100644 _integrations/nrpq/example/main.go delete mode 100644 _integrations/nrpq/example/sqlx/main.go delete mode 100644 _integrations/nrpq/nrpq.go delete mode 100644 _integrations/nrpq/nrpq_test.go delete mode 100644 _integrations/nrsqlite3/README.md delete mode 100644 _integrations/nrsqlite3/example/main.go delete mode 100644 _integrations/nrsqlite3/nrsqlite3.go delete mode 100644 _integrations/nrsqlite3/nrsqlite3_test.go delete mode 100644 _integrations/nrstan/README.md delete mode 100644 _integrations/nrstan/examples/README.md delete mode 100644 _integrations/nrstan/examples/main.go delete mode 100644 _integrations/nrstan/nrstan.go delete mode 100644 _integrations/nrstan/nrstan_doc.go delete mode 100644 _integrations/nrstan/nrstan_test.go delete mode 100644 _integrations/nrzap/README.md delete mode 100644 _integrations/nrzap/example_test.go delete mode 100644 _integrations/nrzap/nrzap.go delete mode 100644 app_run.go delete mode 100644 app_run_test.go delete mode 100644 application.go delete mode 100644 attributes.go delete mode 100644 browser_header.go delete mode 100644 browser_header_test.go delete mode 100644 config.go delete mode 100644 context.go delete mode 100644 context_stub.go delete mode 100644 datastore.go delete mode 100644 doc.go delete mode 100644 errors.go delete mode 100644 examples/README.md delete mode 100644 examples/client-round-tripper/main.go delete mode 100644 examples/client/main.go delete mode 100644 examples/custom-instrumentation/main.go delete mode 100644 examples/server-http/main.go delete mode 100644 examples/server/main.go delete mode 100644 examples/short-lived-process/main.go delete mode 100644 examples_test.go delete mode 100644 instrumentation.go delete mode 100644 internal/adaptive_sampler.go delete mode 100644 internal/adaptive_sampler_test.go delete mode 100644 internal/analytics_events.go delete mode 100644 internal/analytics_events_test.go delete mode 100644 internal/apdex.go delete mode 100644 internal/apdex_test.go delete mode 100644 internal/attributes.go delete mode 100644 internal/attributes_test.go delete mode 100644 internal/browser.go delete mode 100644 internal/browser_test.go delete mode 100644 internal/cat/appdata.go delete mode 100644 internal/cat/appdata_test.go delete mode 100644 internal/cat/errors.go delete mode 100644 internal/cat/headers.go delete mode 100644 internal/cat/id.go delete mode 100644 internal/cat/id_test.go delete mode 100644 internal/cat/path_hash.go delete mode 100644 internal/cat/path_hash_test.go delete mode 100644 internal/cat/synthetics.go delete mode 100644 internal/cat/synthetics_test.go delete mode 100644 internal/cat/txndata.go delete mode 100644 internal/cat/txndata_test.go delete mode 100644 internal/cat_test.go delete mode 100644 internal/collector.go delete mode 100644 internal/collector_test.go delete mode 100644 internal/compress.go delete mode 100644 internal/connect_reply.go delete mode 100644 internal/connect_reply_test.go delete mode 100644 internal/context.go delete mode 100644 internal/cross_process_http.go delete mode 100644 internal/cross_process_http_test.go delete mode 100644 internal/crossagent/README.md delete mode 100644 internal/crossagent/cross_agent_tests/README.md delete mode 100644 internal/crossagent/cross_agent_tests/attribute_configuration.json delete mode 100644 internal/crossagent/cross_agent_tests/cat/README.md delete mode 100644 internal/crossagent/cross_agent_tests/cat/cat_map.json delete mode 100644 internal/crossagent/cross_agent_tests/cat/path_hashing.json delete mode 100644 internal/crossagent/cross_agent_tests/collector_hostname.json delete mode 100644 internal/crossagent/cross_agent_tests/data_collection_server_configuration.json delete mode 100644 internal/crossagent/cross_agent_tests/datastores/README.md delete mode 100644 internal/crossagent/cross_agent_tests/datastores/datastore_api.json delete mode 100644 internal/crossagent/cross_agent_tests/datastores/datastore_instances.json delete mode 100644 internal/crossagent/cross_agent_tests/distributed_tracing/distributed_tracing.json delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/README.md delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/cases.json delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/docker-0.9.1.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/docker-1.0.0.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-lxc-driver.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-native-driver-fs.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-native-driver-systemd.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/docker-1.3.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/docker-custom-prefix.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/docker-too-long.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/empty.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/heroku.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/invalid-characters.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/invalid-length.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/no_cpu_subsystem.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.04-lxc-container.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.04-no-container.txt delete mode 100644 internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.10-no-container.txt delete mode 100644 internal/crossagent/cross_agent_tests/labels.json delete mode 100644 internal/crossagent/cross_agent_tests/language_agents_security_policies.json delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/README.md delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.colon_obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.explain.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.obfuscated.txt delete mode 100644 internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.query.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_1core_1logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_1core_2logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_2core_2logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_4core_4logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_12core_24logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_20core_40logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_2core_2logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_2core_4logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_4core_4logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/4pack_4core_4logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/8pack_8core_8logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/README.md delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/Xpack_Xcore_2logical.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_cpuinfo/malformed_file.txt delete mode 100644 internal/crossagent/cross_agent_tests/proc_meminfo/README.md delete mode 100644 internal/crossagent/cross_agent_tests/proc_meminfo/meminfo_4096MB.txt delete mode 100644 internal/crossagent/cross_agent_tests/rules.json delete mode 100644 internal/crossagent/cross_agent_tests/rum_client_config.json delete mode 100644 internal/crossagent/cross_agent_tests/rum_footer_insertion_location/close-body-in-comment.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_footer_insertion_location/dynamic-iframe.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/basic.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/body_with_attributes.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_after_x_ua_tag.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_before_x_ua_tag.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_with_spaces.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/comments1.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/comments2.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag_after_x_ua_tag.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag_before_x_ua_tag.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes1.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes2.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes_mismatch.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_single_quotes1.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_single_quotes_mismatch.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/head_with_attributes.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/incomplete_non_meta_tags.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_end_header.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_header.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_html_and_no_header.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_start_header.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/script1.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/script2.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_multiline.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_multiple_tags.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_spaces_around_equals.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_with_others.html delete mode 100644 internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_with_spaces.html delete mode 100644 internal/crossagent/cross_agent_tests/sql_obfuscation/README.md delete mode 100644 internal/crossagent/cross_agent_tests/sql_obfuscation/sql_obfuscation.json delete mode 100644 internal/crossagent/cross_agent_tests/sql_parsing.json delete mode 100644 internal/crossagent/cross_agent_tests/synthetics/README.md delete mode 100644 internal/crossagent/cross_agent_tests/synthetics/synthetics.json delete mode 100644 internal/crossagent/cross_agent_tests/transaction_segment_terms.json delete mode 100644 internal/crossagent/cross_agent_tests/url_clean.json delete mode 100644 internal/crossagent/cross_agent_tests/url_domain_extraction.json delete mode 100644 internal/crossagent/cross_agent_tests/utilization/README.md delete mode 100644 internal/crossagent/cross_agent_tests/utilization/boot_id.json delete mode 100644 internal/crossagent/cross_agent_tests/utilization/utilization_json.json delete mode 100644 internal/crossagent/cross_agent_tests/utilization_vendor_specific/README.md delete mode 100644 internal/crossagent/cross_agent_tests/utilization_vendor_specific/aws.json delete mode 100644 internal/crossagent/cross_agent_tests/utilization_vendor_specific/azure.json delete mode 100644 internal/crossagent/cross_agent_tests/utilization_vendor_specific/gcp.json delete mode 100644 internal/crossagent/cross_agent_tests/utilization_vendor_specific/pcf.json delete mode 100644 internal/crossagent/crossagent.go delete mode 100644 internal/custom_event.go delete mode 100644 internal/custom_event_test.go delete mode 100644 internal/custom_events.go delete mode 100644 internal/custom_metric.go delete mode 100644 internal/distributed_tracing.go delete mode 100644 internal/distributed_tracing_test.go delete mode 100644 internal/environment.go delete mode 100644 internal/environment_test.go delete mode 100644 internal/error_events.go delete mode 100644 internal/error_events_test.go delete mode 100644 internal/errors.go delete mode 100644 internal/errors_test.go delete mode 100644 internal/expect.go delete mode 100644 internal/harvest.go delete mode 100644 internal/harvest_test.go delete mode 100644 internal/integrationsupport/integrationsupport.go delete mode 100644 internal/intrinsics.go delete mode 100644 internal/json_object_writer.go delete mode 100644 internal/jsonx/encode.go delete mode 100644 internal/jsonx/encode_test.go delete mode 100644 internal/labels.go delete mode 100644 internal/limits.go delete mode 100644 internal/logger/logger.go delete mode 100644 internal/metric_names.go delete mode 100644 internal/metric_rules.go delete mode 100644 internal/metric_rules_test.go delete mode 100644 internal/metrics.go delete mode 100644 internal/metrics_test.go delete mode 100644 internal/obfuscate.go delete mode 100644 internal/obfuscate_test.go delete mode 100644 internal/priority.go delete mode 100644 internal/priority_test.go delete mode 100644 internal/queuing.go delete mode 100644 internal/queuing_test.go delete mode 100644 internal/rand.go delete mode 100644 internal/rules_cache.go delete mode 100644 internal/rules_cache_test.go delete mode 100644 internal/sampler.go delete mode 100644 internal/sampler_test.go delete mode 100644 internal/security_policies.go delete mode 100644 internal/security_policies_test.go delete mode 100644 internal/segment_terms.go delete mode 100644 internal/segment_terms_test.go delete mode 100644 internal/serverless.go delete mode 100644 internal/serverless_test.go delete mode 100644 internal/slow_queries.go delete mode 100644 internal/slow_queries_test.go delete mode 100644 internal/span_events.go delete mode 100644 internal/span_events_test.go delete mode 100644 internal/sqlparse/sqlparse.go delete mode 100644 internal/sqlparse/sqlparse_test.go delete mode 100644 internal/stack_frame.go delete mode 100644 internal/stack_frame_pre_1_7.go delete mode 100644 internal/stacktrace.go delete mode 100644 internal/stacktrace_test.go delete mode 100644 internal/stacktracetest/stacktracetest.go delete mode 100644 internal/synthetics_test.go delete mode 100644 internal/sysinfo/bootid.go delete mode 100644 internal/sysinfo/docker.go delete mode 100644 internal/sysinfo/docker_test.go delete mode 100644 internal/sysinfo/errors.go delete mode 100644 internal/sysinfo/hostname_generic.go delete mode 100644 internal/sysinfo/hostname_linux.go delete mode 100644 internal/sysinfo/memtotal.go delete mode 100644 internal/sysinfo/memtotal_darwin.go delete mode 100644 internal/sysinfo/memtotal_darwin_test.go delete mode 100644 internal/sysinfo/memtotal_freebsd.go delete mode 100644 internal/sysinfo/memtotal_freebsd_test.go delete mode 100644 internal/sysinfo/memtotal_linux.go delete mode 100644 internal/sysinfo/memtotal_openbsd_amd64.go delete mode 100644 internal/sysinfo/memtotal_openbsd_test.go delete mode 100644 internal/sysinfo/memtotal_solaris.go delete mode 100644 internal/sysinfo/memtotal_solaris_test.go delete mode 100644 internal/sysinfo/memtotal_test.go delete mode 100644 internal/sysinfo/memtotal_windows.go delete mode 100644 internal/sysinfo/usage.go delete mode 100644 internal/sysinfo/usage_posix.go delete mode 100644 internal/sysinfo/usage_windows.go delete mode 100644 internal/tools/interface-wrapping/driver_conn.json delete mode 100644 internal/tools/interface-wrapping/driver_driver.json delete mode 100644 internal/tools/interface-wrapping/driver_stmt.json delete mode 100644 internal/tools/interface-wrapping/main.go delete mode 100644 internal/tools/interface-wrapping/transaction_response_writer.json delete mode 100644 internal/tools/rules/main.go delete mode 100644 internal/tools/uncompress-serverless/main.go delete mode 100644 internal/trace_id_generator.go delete mode 100644 internal/trace_id_generator_test.go delete mode 100644 internal/tracing.go delete mode 100644 internal/tracing_test.go delete mode 100644 internal/txn_cross_process.go delete mode 100644 internal/txn_cross_process_test.go delete mode 100644 internal/txn_events.go delete mode 100644 internal/txn_events_test.go delete mode 100644 internal/txn_trace.go delete mode 100644 internal/txn_trace_test.go delete mode 100644 internal/url.go delete mode 100644 internal/url_test.go delete mode 100644 internal/utilities.go delete mode 100644 internal/utilities_test.go delete mode 100644 internal/utilization/addresses.go delete mode 100644 internal/utilization/aws.go delete mode 100644 internal/utilization/aws_test.go delete mode 100644 internal/utilization/azure.go delete mode 100644 internal/utilization/azure_test.go delete mode 100644 internal/utilization/fqdn.go delete mode 100644 internal/utilization/fqdn_pre18.go delete mode 100644 internal/utilization/gcp.go delete mode 100644 internal/utilization/gcp_test.go delete mode 100644 internal/utilization/pcf.go delete mode 100644 internal/utilization/pcf_test.go delete mode 100644 internal/utilization/provider.go delete mode 100644 internal/utilization/provider_test.go delete mode 100644 internal/utilization/utilization.go delete mode 100644 internal/utilization/utilization_test.go delete mode 100644 internal_app.go delete mode 100644 internal_app_test.go delete mode 100644 internal_attributes_test.go delete mode 100644 internal_benchmark_test.go delete mode 100644 internal_browser_test.go delete mode 100644 internal_config.go delete mode 100644 internal_config_test.go delete mode 100644 internal_context_test.go delete mode 100644 internal_cross_process_test.go delete mode 100644 internal_distributed_trace_test.go delete mode 100644 internal_errors_13_test.go delete mode 100644 internal_errors_stacktrace_test.go delete mode 100644 internal_errors_test.go delete mode 100644 internal_response_writer.go delete mode 100644 internal_response_writer_test.go delete mode 100644 internal_segment_attributes_test.go delete mode 100644 internal_serverless_test.go delete mode 100644 internal_set_web_request_test.go delete mode 100644 internal_set_web_response_test.go delete mode 100644 internal_slow_queries_test.go delete mode 100644 internal_span_events_test.go delete mode 100644 internal_synthetics_test.go delete mode 100644 internal_test.go delete mode 100644 internal_txn.go delete mode 100644 internal_txn_test.go delete mode 100644 log.go delete mode 100644 segments.go delete mode 100644 sql_driver.go delete mode 100644 sql_driver_optional_methods.go delete mode 100644 sql_driver_test.go delete mode 100644 transaction.go delete mode 100644 version.go diff --git a/_integrations/README.md b/_integrations/README.md deleted file mode 100644 index 2ec0fb926..000000000 --- a/_integrations/README.md +++ /dev/null @@ -1,3 +0,0 @@ -The integrations in this directory are for the now deprecated v2 New Relic Go -Agent. Integrations for the most recent v3 version of the agent can be found -in [v3/integrations](../v3/integrations). diff --git a/_integrations/logcontext/README.md b/_integrations/logcontext/README.md deleted file mode 100644 index ba5c24dcb..000000000 --- a/_integrations/logcontext/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# _integrations/logcontext [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext) - -Logs in Context. Each directory represents a different logging plugin. -Plugins allow you to add the context required to your log messages so you can -see linking in the APM UI. - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext). diff --git a/_integrations/logcontext/logcontext.go b/_integrations/logcontext/logcontext.go deleted file mode 100644 index 45032fa52..000000000 --- a/_integrations/logcontext/logcontext.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package logcontext - -import newrelic "github.com/newrelic/go-agent" - -// Keys used for logging context JSON. -const ( - KeyFile = "file.name" - KeyLevel = "log.level" - KeyLine = "line.number" - KeyMessage = "message" - KeyMethod = "method.name" - KeyTimestamp = "timestamp" - KeyTraceID = "trace.id" - KeySpanID = "span.id" - KeyEntityName = "entity.name" - KeyEntityType = "entity.type" - KeyEntityGUID = "entity.guid" - KeyHostname = "hostname" -) - -func metadataMapField(m map[string]interface{}, key, val string) { - if val != "" { - m[key] = val - } -} - -// AddLinkingMetadata adds the LinkingMetadata into a map. Only non-empty -// string fields are included in the map. The specific key names facilitate -// agent logs in context. These keys are: "trace.id", "span.id", -// "entity.name", "entity.type", "entity.guid", and "hostname". -func AddLinkingMetadata(m map[string]interface{}, md newrelic.LinkingMetadata) { - metadataMapField(m, KeyTraceID, md.TraceID) - metadataMapField(m, KeySpanID, md.SpanID) - metadataMapField(m, KeyEntityName, md.EntityName) - metadataMapField(m, KeyEntityType, md.EntityType) - metadataMapField(m, KeyEntityGUID, md.EntityGUID) - metadataMapField(m, KeyHostname, md.Hostname) -} diff --git a/_integrations/logcontext/nrlogrusplugin/README.md b/_integrations/logcontext/nrlogrusplugin/README.md deleted file mode 100644 index e0f36542f..000000000 --- a/_integrations/logcontext/nrlogrusplugin/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/logcontext/nrlogrusplugin [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin) - -Package `nrlogrusplugin` decorates logs for sending to the New Relic backend. - -```go -import "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin). diff --git a/_integrations/logcontext/nrlogrusplugin/example/main.go b/_integrations/logcontext/nrlogrusplugin/example/main.go deleted file mode 100644 index 9ac6e8a1d..000000000 --- a/_integrations/logcontext/nrlogrusplugin/example/main.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "fmt" - "os" - "time" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin" - "github.com/sirupsen/logrus" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func doFunction2(txn newrelic.Transaction, e *logrus.Entry) { - defer newrelic.StartSegment(txn, "doFunction2").End() - e.Error("In doFunction2") -} - -func doFunction1(txn newrelic.Transaction, e *logrus.Entry) { - defer newrelic.StartSegment(txn, "doFunction1").End() - e.Trace("In doFunction1") - doFunction2(txn, e) -} - -func main() { - log := logrus.New() - // To enable New Relic log decoration, use the - // nrlogrusplugin.ContextFormatter{} - log.SetFormatter(nrlogrusplugin.ContextFormatter{}) - log.SetLevel(logrus.TraceLevel) - - log.Debug("Logger created") - - cfg := newrelic.NewConfig("Logrus Log Decoration", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.DistributedTracer.Enabled = true - cfg.CrossApplicationTracer.Enabled = false - - app, err := newrelic.NewApplication(cfg) - if nil != err { - log.Panic("Failed to create application", err) - } - - log.Debug("Application created, waiting for connection") - - err = app.WaitForConnection(10 * time.Second) - if nil != err { - log.Panic("Failed to connect application", err) - } - log.Info("Application connected") - defer app.Shutdown(10 * time.Second) - - log.Debug("Starting transaction now") - txn := app.StartTransaction("main", nil, nil) - - // Add the transaction context to the logger. Only once this happens will - // the logs be properly decorated with all required fields. - e := log.WithContext(newrelic.NewContext(context.Background(), txn)) - - doFunction1(txn, e) - - e.Info("Ending transaction") - txn.End() -} diff --git a/_integrations/logcontext/nrlogrusplugin/nrlogrusplugin.go b/_integrations/logcontext/nrlogrusplugin/nrlogrusplugin.go deleted file mode 100644 index 8702eaf8e..000000000 --- a/_integrations/logcontext/nrlogrusplugin/nrlogrusplugin.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrlogrusplugin decorates logs for sending to the New Relic backend. -// -// Use this package if you want to enable the New Relic logging product and see -// your log messages in the New Relic UI. -// -// Since Logrus is completely api-compatible with the stdlib logger, you can -// replace your `"log"` imports with `log "github.com/sirupsen/logrus"` and -// follow the steps below to enable the logging product for use with the stdlib -// Go logger. -// -// Using `logger.WithField` -// (https://godoc.org/github.com/sirupsen/logrus#Logger.WithField) and -// `logger.WithFields` -// (https://godoc.org/github.com/sirupsen/logrus#Logger.WithFields) is -// supported. However, if the field key collides with one of the keys used by -// the New Relic Formatter, the value will be overwritten. Reserved keys are -// those found in the `logcontext` package -// (https://godoc.org/github.com/newrelic/go-agent/_integrations/logcontext/#pkg-constants). -// -// Supported types for `logger.WithField` and `logger.WithFields` field values -// are numbers, booleans, strings, and errors. Func types are dropped and all -// other types are converted to strings. -// -// Requires v1.4.0 of the Logrus package or newer. -// -// Configuration -// -// For the best linking experience be sure to enable Distributed Tracing: -// -// cfg := NewConfig("Example Application", "__YOUR_NEW_RELIC_LICENSE_KEY__") -// cfg.DistributedTracer.Enabled = true -// -// To enable log decoration, set your log's formatter to the -// `nrlogrusplugin.ContextFormatter` -// -// logger := log.New() -// logger.SetFormatter(nrlogrusplugin.ContextFormatter{}) -// -// or if you are using the logrus standard logger -// -// log.SetFormatter(nrlogrusplugin.ContextFormatter{}) -// -// The logger will now look for a newrelic.Transaction inside its context and -// decorate logs accordingly. Therefore, the Transaction must be added to the -// context and passed to the logger. For example, this logging call -// -// logger.Info("Hello New Relic!") -// -// must be transformed to include the context, such as: -// -// ctx := newrelic.NewContext(context.Background(), txn) -// logger.WithContext(ctx).Info("Hello New Relic!") -// -// Troubleshooting -// -// When properly configured, your log statements will be in JSON format with -// one message per line: -// -// {"message":"Hello New Relic!","log.level":"info","trace.id":"469a04f6c1278593","span.id":"9f365c71f0f04a98","entity.type":"SERVICE","entity.guid":"MTE3ODUwMHxBUE18QVBQTElDQVRJT058Mjc3MDU2Njc1","hostname":"my.hostname","timestamp":1568917432034,"entity.name":"Example Application"} -// -// If the `trace.id` key is missing, be sure that Distributed Tracing is -// enabled and that the Transaction context has been added to the logger using -// `WithContext` (https://godoc.org/github.com/sirupsen/logrus#Logger.WithContext). -package nrlogrusplugin - -import ( - "bytes" - "encoding/json" - "fmt" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/logcontext" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/jsonx" - "github.com/sirupsen/logrus" -) - -func init() { internal.TrackUsage("integration", "logcontext", "logrus") } - -type logFields map[string]interface{} - -// ContextFormatter is a `logrus.Formatter` that will format logs for sending -// to New Relic. -type ContextFormatter struct{} - -// Format renders a single log entry. -func (f ContextFormatter) Format(e *logrus.Entry) ([]byte, error) { - // 12 = 6 from GetLinkingMetadata + 6 more below - data := make(logFields, len(e.Data)+12) - for k, v := range e.Data { - data[k] = v - } - - if ctx := e.Context; nil != ctx { - if txn := newrelic.FromContext(ctx); nil != txn { - logcontext.AddLinkingMetadata(data, txn.GetLinkingMetadata()) - } - } - - data[logcontext.KeyTimestamp] = uint64(e.Time.UnixNano()) / uint64(1000*1000) - data[logcontext.KeyMessage] = e.Message - data[logcontext.KeyLevel] = e.Level - - if e.HasCaller() { - data[logcontext.KeyFile] = e.Caller.File - data[logcontext.KeyLine] = e.Caller.Line - data[logcontext.KeyMethod] = e.Caller.Function - } - - var b *bytes.Buffer - if e.Buffer != nil { - b = e.Buffer - } else { - b = &bytes.Buffer{} - } - writeDataJSON(b, data) - return b.Bytes(), nil -} - -func writeDataJSON(buf *bytes.Buffer, data logFields) { - buf.WriteByte('{') - var needsComma bool - for k, v := range data { - if needsComma { - buf.WriteByte(',') - } else { - needsComma = true - } - jsonx.AppendString(buf, k) - buf.WriteByte(':') - writeValue(buf, v) - } - buf.WriteByte('}') - buf.WriteByte('\n') -} - -func writeValue(buf *bytes.Buffer, val interface{}) { - switch v := val.(type) { - case string: - jsonx.AppendString(buf, v) - case bool: - if v { - buf.WriteString("true") - } else { - buf.WriteString("false") - } - case uint8: - jsonx.AppendInt(buf, int64(v)) - case uint16: - jsonx.AppendInt(buf, int64(v)) - case uint32: - jsonx.AppendInt(buf, int64(v)) - case uint64: - jsonx.AppendInt(buf, int64(v)) - case uint: - jsonx.AppendInt(buf, int64(v)) - case uintptr: - jsonx.AppendInt(buf, int64(v)) - case int8: - jsonx.AppendInt(buf, int64(v)) - case int16: - jsonx.AppendInt(buf, int64(v)) - case int32: - jsonx.AppendInt(buf, int64(v)) - case int: - jsonx.AppendInt(buf, int64(v)) - case int64: - jsonx.AppendInt(buf, v) - case float32: - jsonx.AppendFloat(buf, float64(v)) - case float64: - jsonx.AppendFloat(buf, v) - case logrus.Level: - jsonx.AppendString(buf, v.String()) - case error: - jsonx.AppendString(buf, v.Error()) - default: - if m, ok := v.(json.Marshaler); ok { - if js, err := m.MarshalJSON(); nil == err { - buf.Write(js) - return - } - } - jsonx.AppendString(buf, fmt.Sprintf("%#v", v)) - } -} diff --git a/_integrations/logcontext/nrlogrusplugin/nrlogrusplugin_test.go b/_integrations/logcontext/nrlogrusplugin/nrlogrusplugin_test.go deleted file mode 100644 index cdaa777fc..000000000 --- a/_integrations/logcontext/nrlogrusplugin/nrlogrusplugin_test.go +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrlogrusplugin - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "io" - "testing" - "time" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" - "github.com/newrelic/go-agent/internal/sysinfo" - "github.com/sirupsen/logrus" -) - -var ( - testTime = time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - matchAnything = struct{}{} -) - -func newTestLogger(out io.Writer) *logrus.Logger { - l := logrus.New() - l.Formatter = ContextFormatter{} - l.SetReportCaller(true) - l.SetOutput(out) - return l -} - -func validateOutput(t *testing.T, out *bytes.Buffer, expected map[string]interface{}) { - var actual map[string]interface{} - if err := json.Unmarshal(out.Bytes(), &actual); nil != err { - t.Fatal("failed to unmarshal log output:", err) - } - for k, v := range expected { - found, ok := actual[k] - if !ok { - t.Errorf("key %s not found:\nactual=%s", k, actual) - } - if v != matchAnything && found != v { - t.Errorf("value for key %s is incorrect:\nactual=%s\nexpected=%s", k, found, v) - } - } - for k, v := range actual { - if _, ok := expected[k]; !ok { - t.Errorf("unexpected key found:\nkey=%s\nvalue=%s", k, v) - } - } -} - -func BenchmarkWithOutTransaction(b *testing.B) { - log := newTestLogger(bytes.NewBuffer([]byte(""))) - ctx := context.Background() - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - log.WithContext(ctx).Info("Hello World!") - } -} - -func BenchmarkJSONFormatter(b *testing.B) { - log := newTestLogger(bytes.NewBuffer([]byte(""))) - log.Formatter = new(logrus.JSONFormatter) - ctx := context.Background() - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - log.WithContext(ctx).Info("Hello World!") - } -} - -func BenchmarkTextFormatter(b *testing.B) { - log := newTestLogger(bytes.NewBuffer([]byte(""))) - log.Formatter = new(logrus.TextFormatter) - ctx := context.Background() - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - log.WithContext(ctx).Info("Hello World!") - } -} - -func BenchmarkWithTransaction(b *testing.B) { - app := integrationsupport.NewTestApp(nil, nil) - txn := app.StartTransaction("TestLogDistributedTracingDisabled", nil, nil) - log := newTestLogger(bytes.NewBuffer([]byte(""))) - ctx := newrelic.NewContext(context.Background(), txn) - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - log.WithContext(ctx).Info("Hello World!") - } -} - -func TestLogNoContext(t *testing.T) { - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - log.WithTime(testTime).Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "file.name": matchAnything, - "line.number": matchAnything, - "log.level": "info", - "message": "Hello World!", - "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogNoContext", - "timestamp": float64(1417136460000), - }) -} - -func TestLogNoTxn(t *testing.T) { - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - log.WithTime(testTime).WithContext(context.Background()).Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "file.name": matchAnything, - "line.number": matchAnything, - "log.level": "info", - "message": "Hello World!", - "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogNoTxn", - "timestamp": float64(1417136460000), - }) -} - -func TestLogDistributedTracingDisabled(t *testing.T) { - app := integrationsupport.NewTestApp(nil, nil) - txn := app.StartTransaction("TestLogDistributedTracingDisabled", nil, nil) - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - ctx := newrelic.NewContext(context.Background(), txn) - host, _ := sysinfo.Hostname() - log.WithTime(testTime).WithContext(ctx).Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "entity.name": integrationsupport.SampleAppName, - "entity.type": "SERVICE", - "file.name": matchAnything, - "hostname": host, - "line.number": matchAnything, - "log.level": "info", - "message": "Hello World!", - "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogDistributedTracingDisabled", - "timestamp": float64(1417136460000), - }) -} - -func TestLogSampledFalse(t *testing.T) { - app := integrationsupport.NewTestApp( - func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleNothing{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - }, - func(cfg *newrelic.Config) { - cfg.DistributedTracer.Enabled = true - cfg.CrossApplicationTracer.Enabled = false - }) - txn := app.StartTransaction("TestLogSampledFalse", nil, nil) - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - ctx := newrelic.NewContext(context.Background(), txn) - host, _ := sysinfo.Hostname() - log.WithTime(testTime).WithContext(ctx).Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "entity.name": integrationsupport.SampleAppName, - "entity.type": "SERVICE", - "file.name": matchAnything, - "hostname": host, - "line.number": matchAnything, - "log.level": "info", - "message": "Hello World!", - "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogSampledFalse", - "timestamp": float64(1417136460000), - "trace.id": "d9466896a525ccbf", - }) -} - -func TestLogSampledTrue(t *testing.T) { - app := integrationsupport.NewTestApp( - func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - }, - func(cfg *newrelic.Config) { - cfg.DistributedTracer.Enabled = true - cfg.CrossApplicationTracer.Enabled = false - }) - txn := app.StartTransaction("TestLogSampledTrue", nil, nil) - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - ctx := newrelic.NewContext(context.Background(), txn) - host, _ := sysinfo.Hostname() - log.WithTime(testTime).WithContext(ctx).Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "entity.name": integrationsupport.SampleAppName, - "entity.type": "SERVICE", - "file.name": matchAnything, - "hostname": host, - "line.number": matchAnything, - "log.level": "info", - "message": "Hello World!", - "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestLogSampledTrue", - "span.id": "bcfb32e050b264b8", - "timestamp": float64(1417136460000), - "trace.id": "d9466896a525ccbf", - }) -} - -func TestEntryUsedTwice(t *testing.T) { - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - entry := log.WithTime(testTime) - - // First log has dt enabled, ensure trace.id and span.id are included - app := integrationsupport.NewTestApp( - func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - }, - func(cfg *newrelic.Config) { - cfg.DistributedTracer.Enabled = true - cfg.CrossApplicationTracer.Enabled = false - }) - txn := app.StartTransaction("TestEntryUsedTwice1", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - host, _ := sysinfo.Hostname() - entry.WithContext(ctx).Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "entity.name": integrationsupport.SampleAppName, - "entity.type": "SERVICE", - "file.name": matchAnything, - "hostname": host, - "line.number": matchAnything, - "log.level": "info", - "message": "Hello World!", - "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestEntryUsedTwice", - "span.id": "bcfb32e050b264b8", - "timestamp": float64(1417136460000), - "trace.id": "d9466896a525ccbf", - }) - - // First log has dt enabled, ensure trace.id and span.id are included - out.Reset() - app = integrationsupport.NewTestApp(nil, - func(cfg *newrelic.Config) { - cfg.DistributedTracer.Enabled = false - }) - txn = app.StartTransaction("TestEntryUsedTwice2", nil, nil) - ctx = newrelic.NewContext(context.Background(), txn) - host, _ = sysinfo.Hostname() - entry.WithContext(ctx).Info("Hello World! Again!") - validateOutput(t, out, map[string]interface{}{ - "entity.name": integrationsupport.SampleAppName, - "entity.type": "SERVICE", - "file.name": matchAnything, - "hostname": host, - "line.number": matchAnything, - "log.level": "info", - "message": "Hello World! Again!", - "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestEntryUsedTwice", - "timestamp": float64(1417136460000), - }) -} - -func TestEntryError(t *testing.T) { - app := integrationsupport.NewTestApp(nil, nil) - txn := app.StartTransaction("TestEntryError", nil, nil) - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - ctx := newrelic.NewContext(context.Background(), txn) - host, _ := sysinfo.Hostname() - log.WithTime(testTime).WithContext(ctx).WithField("func", func() {}).Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "entity.name": integrationsupport.SampleAppName, - "entity.type": "SERVICE", - "file.name": matchAnything, - "hostname": host, - "line.number": matchAnything, - "log.level": "info", - // Since the err field on the Entry is private we cannot record it. - //"logrus_error": `can not add field "func"`, - "message": "Hello World!", - "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestEntryError", - "timestamp": float64(1417136460000), - }) -} - -func TestWithCustomField(t *testing.T) { - app := integrationsupport.NewTestApp(nil, nil) - txn := app.StartTransaction("TestWithCustomField", nil, nil) - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - ctx := newrelic.NewContext(context.Background(), txn) - host, _ := sysinfo.Hostname() - log.WithTime(testTime).WithContext(ctx).WithField("zip", "zap").Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "entity.name": integrationsupport.SampleAppName, - "entity.type": "SERVICE", - "file.name": matchAnything, - "hostname": host, - "line.number": matchAnything, - "log.level": "info", - "message": "Hello World!", - "method.name": "github.com/newrelic/go-agent/_integrations/logcontext/nrlogrusplugin.TestWithCustomField", - "timestamp": float64(1417136460000), - "zip": "zap", - }) -} - -func TestCustomFieldTypes(t *testing.T) { - out := bytes.NewBuffer([]byte{}) - - testcases := []struct { - input interface{} - output string - }{ - {input: true, output: "true"}, - {input: false, output: "false"}, - {input: uint8(42), output: "42"}, - {input: uint16(42), output: "42"}, - {input: uint32(42), output: "42"}, - {input: uint(42), output: "42"}, - {input: uintptr(42), output: "42"}, - {input: int8(42), output: "42"}, - {input: int16(42), output: "42"}, - {input: int32(42), output: "42"}, - {input: int64(42), output: "42"}, - {input: float32(42), output: "42"}, - {input: float64(42), output: "42"}, - {input: errors.New("Ooops an error"), output: `"Ooops an error"`}, - {input: []int{1, 2, 3}, output: `"[]int{1, 2, 3}"`}, - } - - for _, test := range testcases { - out.Reset() - writeValue(out, test.input) - if out.String() != test.output { - t.Errorf("Incorrect output written:\nactual=%s\nexpected=%s", - out.String(), test.output) - } - } -} - -func TestUnsetCaller(t *testing.T) { - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - log.SetReportCaller(false) - log.WithTime(testTime).Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "log.level": "info", - "message": "Hello World!", - "timestamp": float64(1417136460000), - }) -} - -func TestCustomFieldNameCollision(t *testing.T) { - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - log.SetReportCaller(false) - log.WithTime(testTime).WithField("timestamp", "Yesterday").Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "log.level": "info", - "message": "Hello World!", - // Reserved keys will be overwritten - "timestamp": float64(1417136460000), - }) -} - -type gopher struct { - name string -} - -func (g *gopher) MarshalJSON() ([]byte, error) { - return json.Marshal(g.name) -} - -func TestCustomJSONMarshaller(t *testing.T) { - out := bytes.NewBuffer([]byte{}) - log := newTestLogger(out) - log.SetReportCaller(false) - log.WithTime(testTime).WithField("gopher", &gopher{name: "sam"}).Info("Hello World!") - validateOutput(t, out, map[string]interface{}{ - "gopher": "sam", - "log.level": "info", - "message": "Hello World!", - "timestamp": float64(1417136460000), - }) -} diff --git a/_integrations/nrawssdk/README.md b/_integrations/nrawssdk/README.md deleted file mode 100644 index 3886ce4f7..000000000 --- a/_integrations/nrawssdk/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# _integrations/nrawssdk [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrawssdk?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrawssdk) - -Integrations for AWS SDKs versions 1 and 2. diff --git a/_integrations/nrawssdk/internal/internal.go b/_integrations/nrawssdk/internal/internal.go deleted file mode 100644 index 924206c63..000000000 --- a/_integrations/nrawssdk/internal/internal.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "context" - "net/http" - "reflect" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -type contextKeyType struct{} - -var segmentContextKey = contextKeyType(struct{}{}) - -type endable interface{ End() error } - -func getTableName(params interface{}) string { - var tableName string - - v := reflect.ValueOf(params) - if v.IsValid() && v.Kind() == reflect.Ptr { - e := v.Elem() - if e.Kind() == reflect.Struct { - n := e.FieldByName("TableName") - if n.IsValid() { - if name, ok := n.Interface().(*string); ok { - if nil != name { - tableName = *name - } - } - } - } - } - - return tableName -} - -func getRequestID(hdr http.Header) string { - id := hdr.Get("X-Amzn-Requestid") - if id == "" { - // Alternative version of request id in the header - id = hdr.Get("X-Amz-Request-Id") - } - return id -} - -// StartSegmentInputs is used as the input to StartSegment. -type StartSegmentInputs struct { - HTTPRequest *http.Request - ServiceName string - Operation string - Region string - Params interface{} -} - -// StartSegment starts a segment of either type DatastoreSegment or -// ExternalSegment given the serviceName provided. The segment is then added to -// the request context. -func StartSegment(input StartSegmentInputs) *http.Request { - - httpCtx := input.HTTPRequest.Context() - txn := newrelic.FromContext(httpCtx) - - var segment endable - // Service name capitalization is different for v1 and v2. - if input.ServiceName == "dynamodb" || input.ServiceName == "DynamoDB" { - segment = &newrelic.DatastoreSegment{ - Product: newrelic.DatastoreDynamoDB, - Collection: getTableName(input.Params), - Operation: input.Operation, - ParameterizedQuery: "", - QueryParameters: nil, - Host: input.HTTPRequest.URL.Host, - PortPathOrID: input.HTTPRequest.URL.Port(), - DatabaseName: "", - StartTime: newrelic.StartSegmentNow(txn), - } - } else { - segment = newrelic.StartExternalSegment(txn, input.HTTPRequest) - } - - integrationsupport.AddAgentSpanAttribute(txn, newrelic.SpanAttributeAWSOperation, input.Operation) - integrationsupport.AddAgentSpanAttribute(txn, newrelic.SpanAttributeAWSRegion, input.Region) - - ctx := context.WithValue(httpCtx, segmentContextKey, segment) - return input.HTTPRequest.WithContext(ctx) -} - -// EndSegment will end any segment found in the given context. -func EndSegment(ctx context.Context, hdr http.Header) { - if segment, ok := ctx.Value(segmentContextKey).(endable); ok { - if id := getRequestID(hdr); "" != id { - txn := newrelic.FromContext(ctx) - integrationsupport.AddAgentSpanAttribute(txn, newrelic.SpanAttributeAWSRequestID, id) - } - segment.End() - } -} diff --git a/_integrations/nrawssdk/internal/internal_test.go b/_integrations/nrawssdk/internal/internal_test.go deleted file mode 100644 index 4bcb9f907..000000000 --- a/_integrations/nrawssdk/internal/internal_test.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "net/http" - "strings" - "testing" - - requestv2 "github.com/aws/aws-sdk-go-v2/aws" - restv2 "github.com/aws/aws-sdk-go-v2/private/protocol/rest" - "github.com/aws/aws-sdk-go-v2/service/lambda" - requestv1 "github.com/aws/aws-sdk-go/aws/request" - restv1 "github.com/aws/aws-sdk-go/private/protocol/rest" -) - -func TestGetTableName(t *testing.T) { - str := "this is a string" - var emptyStr string - strPtr := &str - emptyStrPtr := &emptyStr - - testcases := []struct { - params interface{} - expected string - }{ - {params: nil, expected: ""}, - {params: str, expected: ""}, - {params: strPtr, expected: ""}, - {params: struct{ other string }{other: str}, expected: ""}, - {params: &struct{ other string }{other: str}, expected: ""}, - {params: struct{ TableName bool }{TableName: true}, expected: ""}, - {params: &struct{ TableName bool }{TableName: true}, expected: ""}, - {params: struct{ TableName string }{TableName: str}, expected: ""}, - {params: &struct{ TableName string }{TableName: str}, expected: ""}, - {params: struct{ TableName *string }{TableName: nil}, expected: ""}, - {params: &struct{ TableName *string }{TableName: nil}, expected: ""}, - {params: struct{ TableName *string }{TableName: emptyStrPtr}, expected: ""}, - {params: &struct{ TableName *string }{TableName: emptyStrPtr}, expected: ""}, - {params: struct{ TableName *string }{TableName: strPtr}, expected: ""}, - {params: &struct{ TableName *string }{TableName: strPtr}, expected: str}, - } - - for i, test := range testcases { - if out := getTableName(test.params); test.expected != out { - t.Error(i, out, test.params, test.expected) - } - } -} - -func TestGetRequestID(t *testing.T) { - primary := "X-Amzn-Requestid" - secondary := "X-Amz-Request-Id" - - testcases := []struct { - hdr http.Header - expected string - }{ - {hdr: http.Header{ - "hello": []string{"world"}, - }, expected: ""}, - - {hdr: http.Header{ - strings.ToUpper(primary): []string{"hello"}, - }, expected: ""}, - - {hdr: http.Header{ - primary: []string{"hello"}, - }, expected: "hello"}, - - {hdr: http.Header{ - secondary: []string{"hello"}, - }, expected: "hello"}, - - {hdr: http.Header{ - primary: []string{"hello"}, - secondary: []string{"world"}, - }, expected: "hello"}, - } - - for i, test := range testcases { - if out := getRequestID(test.hdr); test.expected != out { - t.Error(i, out, test.hdr, test.expected) - } - } - - // Make sure our assumptions still hold against aws-sdk-go - for _, test := range testcases { - req := &requestv1.Request{ - HTTPResponse: &http.Response{ - Header: test.hdr, - }, - } - restv1.UnmarshalMeta(req) - if out := getRequestID(test.hdr); req.RequestID != out { - t.Error("requestId assumptions incorrect", out, req.RequestID, - test.hdr, test.expected) - } - } - - // Make sure our assumptions still hold against aws-sdk-go-v2 - for _, test := range testcases { - req := &requestv2.Request{ - HTTPResponse: &http.Response{ - Header: test.hdr, - }, - Data: &lambda.InvokeOutput{}, - } - restv2.UnmarshalMeta(req) - if out := getRequestID(test.hdr); req.RequestID != out { - t.Error("requestId assumptions incorrect", out, req.RequestID, - test.hdr, test.expected) - } - } -} diff --git a/_integrations/nrawssdk/v1/README.md b/_integrations/nrawssdk/v1/README.md deleted file mode 100644 index c05194fb8..000000000 --- a/_integrations/nrawssdk/v1/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrawssdk/v1 [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrawssdk/v1?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrawssdk/v1) - -Package `nrawssdk` instruments https://github.com/aws/aws-sdk-go requests. - -```go -import "github.com/newrelic/go-agent/_integrations/nrawssdk/v1" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrawssdk/v1). diff --git a/_integrations/nrawssdk/v1/nrawssdk.go b/_integrations/nrawssdk/v1/nrawssdk.go deleted file mode 100644 index b088c1cd5..000000000 --- a/_integrations/nrawssdk/v1/nrawssdk.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrawssdk instruments https://github.com/aws/aws-sdk-go requests. -package nrawssdk - -import ( - "github.com/aws/aws-sdk-go/aws/request" - internal "github.com/newrelic/go-agent/_integrations/nrawssdk/internal" - agentinternal "github.com/newrelic/go-agent/internal" -) - -func init() { agentinternal.TrackUsage("integration", "library", "aws-sdk-go") } - -func startSegment(req *request.Request) { - input := internal.StartSegmentInputs{ - HTTPRequest: req.HTTPRequest, - ServiceName: req.ClientInfo.ServiceName, - Operation: req.Operation.Name, - Region: req.ClientInfo.SigningRegion, - Params: req.Params, - } - req.HTTPRequest = internal.StartSegment(input) -} - -func endSegment(req *request.Request) { - ctx := req.HTTPRequest.Context() - internal.EndSegment(ctx, req.HTTPResponse.Header) -} - -// InstrumentHandlers will add instrumentation to the given *request.Handlers. -// -// A Segment will be created for each out going request. The Transaction must -// be added to the `http.Request`'s Context in order for the segment to be -// recorded. For DynamoDB calls, these segments will be -// `newrelic.DatastoreSegment` type and for all others they will be -// `newrelic.ExternalSegment` type. -// -// Additional attributes will be added to Transaction Trace Segments and Span -// Events: aws.region, aws.requestId, and aws.operation. -// -// To add instrumentation to the Session and see segments created for each -// invocation that uses the Session, call InstrumentHandlers with the session's -// Handlers and add the current Transaction to the `http.Request`'s Context: -// -// ses := session.New() -// // Add instrumentation to handlers -// nrawssdk.InstrumentHandlers(&ses.Handlers) -// lambdaClient = lambda.New(ses, aws.NewConfig()) -// -// req, out := lambdaClient.InvokeRequest(&lambda.InvokeInput{ -// ClientContext: aws.String("MyApp"), -// FunctionName: aws.String("Function"), -// InvocationType: aws.String("Event"), -// LogType: aws.String("Tail"), -// Payload: []byte("{}"), -// } -// // Add txn to http.Request's context -// req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, txn) -// err := req.Send() -// -// To add instrumentation to a Request and see a segment created just for the -// individual request, call InstrumentHandlers with the `request.Request`'s -// Handlers and add the current Transaction to the `http.Request`'s Context: -// -// req, out := lambdaClient.InvokeRequest(&lambda.InvokeInput{ -// ClientContext: aws.String("MyApp"), -// FunctionName: aws.String("Function"), -// InvocationType: aws.String("Event"), -// LogType: aws.String("Tail"), -// Payload: []byte("{}"), -// } -// // Add instrumentation to handlers -// nrawssdk.InstrumentHandlers(&req.Handlers) -// // Add txn to http.Request's context -// req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, txn) -// err := req.Send() -func InstrumentHandlers(handlers *request.Handlers) { - handlers.Send.SetFrontNamed(request.NamedHandler{ - Name: "StartNewRelicSegment", - Fn: startSegment, - }) - handlers.Send.SetBackNamed(request.NamedHandler{ - Name: "EndNewRelicSegment", - Fn: endSegment, - }) -} diff --git a/_integrations/nrawssdk/v1/nrawssdk_test.go b/_integrations/nrawssdk/v1/nrawssdk_test.go deleted file mode 100644 index 48bb5976c..000000000 --- a/_integrations/nrawssdk/v1/nrawssdk_test.go +++ /dev/null @@ -1,589 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrawssdk - -import ( - "bytes" - "errors" - "io/ioutil" - "net/http" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/lambda" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -func testApp() integrationsupport.ExpectApp { - return integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, integrationsupport.DTEnabledCfgFn) -} - -type fakeTransport struct{} - -func (t fakeTransport) RoundTrip(r *http.Request) (*http.Response, error) { - return &http.Response{ - Status: "200 OK", - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), - Header: http.Header{ - "X-Amzn-Requestid": []string{requestID}, - }, - }, nil -} - -type fakeCreds struct{} - -func (c *fakeCreds) Retrieve() (credentials.Value, error) { - return credentials.Value{}, nil -} -func (c *fakeCreds) IsExpired() bool { return false } - -func newSession() *session.Session { - r := "us-west-2" - ses := session.New() - ses.Config.Credentials = credentials.NewCredentials(&fakeCreds{}) - ses.Config.HTTPClient.Transport = &fakeTransport{} - ses.Config.Region = &r - return ses -} - -const ( - requestID = "testing request id" - txnName = "aws-txn" -) - -var ( - genericSpan = internal.WantEvent{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/" + txnName, - "sampled": true, - "category": "generic", - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "transactionId": internal.MatchAnything, - "nr.entryPoint": true, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - } - externalSpan = internal.WantEvent{ - Intrinsics: map[string]interface{}{ - "name": "External/lambda.us-west-2.amazonaws.com/http/POST", - "sampled": true, - "category": "http", - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "transactionId": internal.MatchAnything, - "traceId": internal.MatchAnything, - "parentId": internal.MatchAnything, - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.operation": "Invoke", - "aws.region": "us-west-2", - "aws.requestId": requestID, - "http.method": "POST", - "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", - }, - } - externalSpanNoRequestID = internal.WantEvent{ - Intrinsics: map[string]interface{}{ - "name": "External/lambda.us-west-2.amazonaws.com/http/POST", - "sampled": true, - "category": "http", - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "transactionId": internal.MatchAnything, - "traceId": internal.MatchAnything, - "parentId": internal.MatchAnything, - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.operation": "Invoke", - "aws.region": "us-west-2", - "http.method": "POST", - "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", - }, - } - datastoreSpan = internal.WantEvent{ - Intrinsics: map[string]interface{}{ - "name": "Datastore/statement/DynamoDB/thebesttable/DescribeTable", - "sampled": true, - "category": "datastore", - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "transactionId": internal.MatchAnything, - "traceId": internal.MatchAnything, - "parentId": internal.MatchAnything, - "component": "DynamoDB", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.operation": "DescribeTable", - "aws.region": "us-west-2", - "aws.requestId": requestID, - "db.collection": "thebesttable", - "db.statement": "'DescribeTable' on 'thebesttable' using 'DynamoDB'", - "peer.address": "dynamodb.us-west-2.amazonaws.com:unknown", - "peer.hostname": "dynamodb.us-west-2.amazonaws.com", - }, - } - - txnMetrics = []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/" + txnName, Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/" + txnName, Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - } - externalMetrics = append([]internal.WantMetric{ - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/lambda.us-west-2.amazonaws.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/lambda.us-west-2.amazonaws.com/http/POST", Scope: "OtherTransaction/Go/" + txnName, Forced: false, Data: nil}, - }, txnMetrics...) - datastoreMetrics = append([]internal.WantMetric{ - {Name: "Datastore/DynamoDB/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/DynamoDB/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/instance/DynamoDB/dynamodb.us-west-2.amazonaws.com/unknown", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/operation/DynamoDB/DescribeTable", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/DynamoDB/thebesttable/DescribeTable", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/DynamoDB/thebesttable/DescribeTable", Scope: "OtherTransaction/Go/" + txnName, Forced: false, Data: nil}, - }, txnMetrics...) -) - -func TestInstrumentRequestExternal(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := lambda.New(newSession()) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: aws.String("Event"), - LogType: aws.String("Tail"), - Payload: []byte("{}"), - } - - req, out := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, txn) - - err := req.Send() - if nil != err { - t.Error(err) - } - if 200 != *out.StatusCode { - t.Error("wrong status code on response", out.StatusCode) - } - - txn.End() - - app.ExpectMetrics(t, externalMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpan}) -} - -func TestInstrumentRequestDatastore(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := dynamodb.New(newSession()) - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req, _ := client.DescribeTableRequest(input) - InstrumentHandlers(&req.Handlers) - req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, txn) - - err := req.Send() - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, datastoreMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, datastoreSpan}) -} - -func TestInstrumentRequestExternalNoTxn(t *testing.T) { - client := lambda.New(newSession()) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: aws.String("Event"), - LogType: aws.String("Tail"), - Payload: []byte("{}"), - } - - req, out := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - - err := req.Send() - if nil != err { - t.Error(err) - } - if 200 != *out.StatusCode { - t.Error("wrong status code on response", out.StatusCode) - } -} - -func TestInstrumentRequestDatastoreNoTxn(t *testing.T) { - client := dynamodb.New(newSession()) - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req, _ := client.DescribeTableRequest(input) - InstrumentHandlers(&req.Handlers) - - err := req.Send() - if nil != err { - t.Error(err) - } -} - -func TestInstrumentSessionExternal(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - ses := newSession() - InstrumentHandlers(&ses.Handlers) - client := lambda.New(ses) - - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: aws.String("Event"), - LogType: aws.String("Tail"), - Payload: []byte("{}"), - } - - req, out := client.InvokeRequest(input) - req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, txn) - - err := req.Send() - if nil != err { - t.Error(err) - } - if 200 != *out.StatusCode { - t.Error("wrong status code on response", out.StatusCode) - } - - txn.End() - - app.ExpectMetrics(t, externalMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpan}) -} - -func TestInstrumentSessionDatastore(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - ses := newSession() - InstrumentHandlers(&ses.Handlers) - client := dynamodb.New(ses) - - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req, _ := client.DescribeTableRequest(input) - req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, txn) - - err := req.Send() - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, datastoreMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, datastoreSpan}) -} - -func TestInstrumentSessionExternalNoTxn(t *testing.T) { - ses := newSession() - InstrumentHandlers(&ses.Handlers) - client := lambda.New(ses) - - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: aws.String("Event"), - LogType: aws.String("Tail"), - Payload: []byte("{}"), - } - - req, out := client.InvokeRequest(input) - req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, nil) - - err := req.Send() - if nil != err { - t.Error(err) - } - if 200 != *out.StatusCode { - t.Error("wrong status code on response", out.StatusCode) - } -} - -func TestInstrumentSessionDatastoreNoTxn(t *testing.T) { - ses := newSession() - InstrumentHandlers(&ses.Handlers) - client := dynamodb.New(ses) - - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req, _ := client.DescribeTableRequest(input) - req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, nil) - - err := req.Send() - if nil != err { - t.Error(err) - } -} - -func TestInstrumentSessionExternalTxnNotInCtx(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - ses := newSession() - InstrumentHandlers(&ses.Handlers) - client := lambda.New(ses) - - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: aws.String("Event"), - LogType: aws.String("Tail"), - Payload: []byte("{}"), - } - - req, out := client.InvokeRequest(input) - - err := req.Send() - if nil != err { - t.Error(err) - } - if 200 != *out.StatusCode { - t.Error("wrong status code on response", out.StatusCode) - } - - txn.End() - - app.ExpectMetrics(t, txnMetrics) -} - -func TestInstrumentSessionDatastoreTxnNotInCtx(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - ses := newSession() - InstrumentHandlers(&ses.Handlers) - client := dynamodb.New(ses) - - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req, _ := client.DescribeTableRequest(input) - - err := req.Send() - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, txnMetrics) -} - -func TestDoublyInstrumented(t *testing.T) { - hs := &request.Handlers{} - if found := hs.Send.Len(); 0 != found { - t.Error("unexpected number of Send handlers found:", found) - } - - InstrumentHandlers(hs) - if found := hs.Send.Len(); 2 != found { - t.Error("unexpected number of Send handlers found:", found) - } - - InstrumentHandlers(hs) - if found := hs.Send.Len(); 2 != found { - t.Error("unexpected number of Send handlers found:", found) - } -} - -type firstFailingTransport struct { - failing bool -} - -func (t *firstFailingTransport) RoundTrip(r *http.Request) (*http.Response, error) { - if t.failing { - t.failing = false - return nil, errors.New("Oops this failed") - } - return &http.Response{ - Status: "200 OK", - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), - Header: http.Header{ - "X-Amzn-Requestid": []string{requestID}, - }, - }, nil -} - -func TestRetrySend(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - ses := newSession() - ses.Config.HTTPClient.Transport = &firstFailingTransport{failing: true} - - client := lambda.New(ses) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: aws.String("Event"), - LogType: aws.String("Tail"), - Payload: []byte("{}"), - } - - req, out := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, txn) - - err := req.Send() - if nil != err { - t.Error(err) - } - if 200 != *out.StatusCode { - t.Error("wrong status code on response", out.StatusCode) - } - - txn.End() - - app.ExpectMetrics(t, externalMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpanNoRequestID, externalSpan}) -} - -func TestRequestSentTwice(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := lambda.New(newSession()) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: aws.String("Event"), - LogType: aws.String("Tail"), - Payload: []byte("{}"), - } - - req, out := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, txn) - - firstErr := req.Send() - if nil != firstErr { - t.Error(firstErr) - } - if 200 != *out.StatusCode { - t.Error("wrong status code on response", out.StatusCode) - } - - secondErr := req.Send() - if nil != secondErr { - t.Error(secondErr) - } - if 200 != *out.StatusCode { - t.Error("wrong status code on response", out.StatusCode) - } - - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: []float64{2}}, - {Name: "External/allOther", Scope: "", Forced: true, Data: []float64{2}}, - {Name: "External/lambda.us-west-2.amazonaws.com/all", Scope: "", Forced: false, Data: []float64{2}}, - {Name: "External/lambda.us-west-2.amazonaws.com/http/POST", Scope: "OtherTransaction/Go/" + txnName, Forced: false, Data: []float64{2}}, - {Name: "OtherTransaction/Go/" + txnName, Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/" + txnName, Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpan, externalSpan}) -} - -type noRequestIDTransport struct{} - -func (t *noRequestIDTransport) RoundTrip(r *http.Request) (*http.Response, error) { - return &http.Response{ - Status: "200 OK", - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), - }, nil -} - -func TestNoRequestIDFound(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - ses := newSession() - ses.Config.HTTPClient.Transport = &noRequestIDTransport{} - - client := lambda.New(ses) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: aws.String("Event"), - LogType: aws.String("Tail"), - Payload: []byte("{}"), - } - - req, out := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - req.HTTPRequest = newrelic.RequestWithTransactionContext(req.HTTPRequest, txn) - - err := req.Send() - if nil != err { - t.Error(err) - } - if 200 != *out.StatusCode { - t.Error("wrong status code on response", out.StatusCode) - } - - txn.End() - - app.ExpectMetrics(t, externalMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpanNoRequestID}) -} diff --git a/_integrations/nrawssdk/v2/README.md b/_integrations/nrawssdk/v2/README.md deleted file mode 100644 index dc2ca26ad..000000000 --- a/_integrations/nrawssdk/v2/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrawssdk/v2 [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrawssdk/v2?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrawssdk/v2) - -Package `nrawssdk` instruments https://github.com/aws/aws-sdk-go-v2 requests. - -```go -import "github.com/newrelic/go-agent/_integrations/nrawssdk/v2" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrawssdk/v2). diff --git a/_integrations/nrawssdk/v2/nrawssdk.go b/_integrations/nrawssdk/v2/nrawssdk.go deleted file mode 100644 index 4b2300a0d..000000000 --- a/_integrations/nrawssdk/v2/nrawssdk.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrawssdk instruments https://github.com/aws/aws-sdk-go-v2 requests. -package nrawssdk - -import ( - "github.com/aws/aws-sdk-go-v2/aws" - internal "github.com/newrelic/go-agent/_integrations/nrawssdk/internal" - agentinternal "github.com/newrelic/go-agent/internal" -) - -func init() { agentinternal.TrackUsage("integration", "library", "aws-sdk-go-v2") } - -func startSegment(req *aws.Request) { - input := internal.StartSegmentInputs{ - HTTPRequest: req.HTTPRequest, - ServiceName: req.Metadata.ServiceName, - Operation: req.Operation.Name, - Region: req.Metadata.SigningRegion, - Params: req.Params, - } - req.HTTPRequest = internal.StartSegment(input) -} - -func endSegment(req *aws.Request) { - ctx := req.HTTPRequest.Context() - internal.EndSegment(ctx, req.HTTPResponse.Header) -} - -// InstrumentHandlers will add instrumentation to the given *aws.Handlers. -// -// A Segment will be created for each out going request. The Transaction must -// be added to the `http.Request`'s Context in order for the segment to be -// recorded. For DynamoDB calls, these segments will be -// `newrelic.DatastoreSegment` type and for all others they will be -// `newrelic.ExternalSegment` type. -// -// Additional attributes will be added to Transaction Trace Segments and Span -// Events: aws.region, aws.requestId, and aws.operation. -// -// To add instrumentation to a Config and see segments created for each -// invocation that uses that Config, call InstrumentHandlers with the config's -// Handlers and add the current Transaction to the `http.Request`'s Context: -// -// cfg, _ := external.LoadDefaultAWSConfig() -// cfg.Region = "us-west-2" -// // Add instrumentation to handlers -// nrawssdk.InstrumentHandlers(&cfg.Handlers) -// lambdaClient = lambda.New(cfg) -// -// req := lambdaClient.InvokeRequest(&lambda.InvokeInput{ -// ClientContext: aws.String("MyApp"), -// FunctionName: aws.String("Function"), -// InvocationType: lambda.InvocationTypeEvent, -// LogType: lambda.LogTypeTail, -// Payload: []byte("{}"), -// } -// // Add txn to http.Request's context -// ctx := newrelic.NewContext(req.Context(), txn) -// resp, err := req.Send(ctx) -// -// To add instrumentation to a Request and see a segment created just for the -// individual request, call InstrumentHandlers with the `aws.Request`'s -// Handlers and add the current Transaction to the `http.Request`'s Context: -// -// req := lambdaClient.InvokeRequest(&lambda.InvokeInput{ -// ClientContext: aws.String("MyApp"), -// FunctionName: aws.String("Function"), -// InvocationType: lambda.InvocationTypeEvent, -// LogType: lambda.LogTypeTail, -// Payload: []byte("{}"), -// } -// // Add instrumentation to handlers -// nrawssdk.InstrumentHandlers(&req.Handlers) -// // Add txn to http.Request's context -// ctx := newrelic.NewContext(req.Context(), txn) -// resp, err := req.Send(ctx) -func InstrumentHandlers(handlers *aws.Handlers) { - handlers.Send.SetFrontNamed(aws.NamedHandler{ - Name: "StartNewRelicSegment", - Fn: startSegment, - }) - handlers.Send.SetBackNamed(aws.NamedHandler{ - Name: "EndNewRelicSegment", - Fn: endSegment, - }) -} diff --git a/_integrations/nrawssdk/v2/nrawssdk_test.go b/_integrations/nrawssdk/v2/nrawssdk_test.go deleted file mode 100644 index c01269412..000000000 --- a/_integrations/nrawssdk/v2/nrawssdk_test.go +++ /dev/null @@ -1,582 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrawssdk - -import ( - "bytes" - "context" - "errors" - "io/ioutil" - "net/http" - "testing" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/aws/external" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - "github.com/aws/aws-sdk-go-v2/service/lambda" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -func testApp() integrationsupport.ExpectApp { - return integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, integrationsupport.DTEnabledCfgFn) -} - -type fakeTransport struct{} - -func (t fakeTransport) RoundTrip(r *http.Request) (*http.Response, error) { - return &http.Response{ - Status: "200 OK", - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), - Header: http.Header{ - "X-Amzn-Requestid": []string{requestID}, - }, - }, nil -} - -type fakeCredsWithoutContext struct{} - -func (c fakeCredsWithoutContext) Retrieve() (aws.Credentials, error) { - return aws.Credentials{}, nil -} - -type fakeCredsWithContext struct{} - -func (c fakeCredsWithContext) Retrieve(ctx context.Context) (aws.Credentials, error) { - return aws.Credentials{}, nil -} - -var fakeCreds = func() interface{} { - var c interface{} = fakeCredsWithoutContext{} - if _, ok := c.(aws.CredentialsProvider); ok { - return c - } - return fakeCredsWithContext{} -}() - -func newConfig(instrument bool) aws.Config { - cfg, _ := external.LoadDefaultAWSConfig() - cfg.Credentials = fakeCreds.(aws.CredentialsProvider) - cfg.Region = "us-west-2" - cfg.HTTPClient = &http.Client{ - Transport: &fakeTransport{}, - } - - if instrument { - InstrumentHandlers(&cfg.Handlers) - } - return cfg -} - -const ( - requestID = "testing request id" - txnName = "aws-txn" -) - -var ( - genericSpan = internal.WantEvent{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/" + txnName, - "sampled": true, - "category": "generic", - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "transactionId": internal.MatchAnything, - "nr.entryPoint": true, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - } - externalSpan = internal.WantEvent{ - Intrinsics: map[string]interface{}{ - "name": "External/lambda.us-west-2.amazonaws.com/http/POST", - "sampled": true, - "category": "http", - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "transactionId": internal.MatchAnything, - "traceId": internal.MatchAnything, - "parentId": internal.MatchAnything, - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.operation": "Invoke", - "aws.region": "us-west-2", - "aws.requestId": requestID, - "http.method": "POST", - "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", - }, - } - externalSpanNoRequestID = internal.WantEvent{ - Intrinsics: map[string]interface{}{ - "name": "External/lambda.us-west-2.amazonaws.com/http/POST", - "sampled": true, - "category": "http", - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "transactionId": internal.MatchAnything, - "traceId": internal.MatchAnything, - "parentId": internal.MatchAnything, - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.operation": "Invoke", - "aws.region": "us-west-2", - "http.method": "POST", - "http.url": "https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/non-existent-function/invocations", - }, - } - datastoreSpan = internal.WantEvent{ - Intrinsics: map[string]interface{}{ - "name": "Datastore/statement/DynamoDB/thebesttable/DescribeTable", - "sampled": true, - "category": "datastore", - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "transactionId": internal.MatchAnything, - "traceId": internal.MatchAnything, - "parentId": internal.MatchAnything, - "component": "DynamoDB", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.operation": "DescribeTable", - "aws.region": "us-west-2", - "aws.requestId": requestID, - "db.collection": "thebesttable", - "db.statement": "'DescribeTable' on 'thebesttable' using 'DynamoDB'", - "peer.address": "dynamodb.us-west-2.amazonaws.com:unknown", - "peer.hostname": "dynamodb.us-west-2.amazonaws.com", - }, - } - - txnMetrics = []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/" + txnName, Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/" + txnName, Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - } - externalMetrics = append(txnMetrics, []internal.WantMetric{ - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/lambda.us-west-2.amazonaws.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/lambda.us-west-2.amazonaws.com/http/POST", Scope: "OtherTransaction/Go/" + txnName, Forced: false, Data: nil}, - }...) - datastoreMetrics = append(txnMetrics, []internal.WantMetric{ - {Name: "Datastore/DynamoDB/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/DynamoDB/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/instance/DynamoDB/dynamodb.us-west-2.amazonaws.com/unknown", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/operation/DynamoDB/DescribeTable", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/DynamoDB/thebesttable/DescribeTable", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/DynamoDB/thebesttable/DescribeTable", Scope: "OtherTransaction/Go/" + txnName, Forced: false, Data: nil}, - }...) -) - -func TestInstrumentRequestExternal(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := lambda.New(newConfig(false)) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, externalMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpan}) -} - -func TestInstrumentRequestDatastore(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := dynamodb.New(newConfig(false)) - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req := client.DescribeTableRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, datastoreMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, datastoreSpan}) -} - -func TestInstrumentRequestExternalNoTxn(t *testing.T) { - client := lambda.New(newConfig(false)) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } -} - -func TestInstrumentRequestDatastoreNoTxn(t *testing.T) { - client := dynamodb.New(newConfig(false)) - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req := client.DescribeTableRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } -} - -func TestInstrumentConfigExternal(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := lambda.New(newConfig(true)) - - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - - req := client.InvokeRequest(input) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, externalMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpan}) -} - -func TestInstrumentConfigDatastore(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := dynamodb.New(newConfig(true)) - - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req := client.DescribeTableRequest(input) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, datastoreMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, datastoreSpan}) -} - -func TestInstrumentConfigExternalNoTxn(t *testing.T) { - client := lambda.New(newConfig(true)) - - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - - req := client.InvokeRequest(input) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } -} - -func TestInstrumentConfigDatastoreNoTxn(t *testing.T) { - client := dynamodb.New(newConfig(true)) - - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req := client.DescribeTableRequest(input) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } -} - -func TestInstrumentConfigExternalTxnNotInCtx(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := lambda.New(newConfig(true)) - - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - - req := client.InvokeRequest(input) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, txnMetrics) -} - -func TestInstrumentConfigDatastoreTxnNotInCtx(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := dynamodb.New(newConfig(true)) - - input := &dynamodb.DescribeTableInput{ - TableName: aws.String("thebesttable"), - } - - req := client.DescribeTableRequest(input) - ctx := req.Context() - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, txnMetrics) -} - -func TestDoublyInstrumented(t *testing.T) { - hs := &aws.Handlers{} - if found := hs.Send.Len(); 0 != found { - t.Error("unexpected number of Send handlers found:", found) - } - - InstrumentHandlers(hs) - if found := hs.Send.Len(); 2 != found { - t.Error("unexpected number of Send handlers found:", found) - } - - InstrumentHandlers(hs) - if found := hs.Send.Len(); 2 != found { - t.Error("unexpected number of Send handlers found:", found) - } -} - -type firstFailingTransport struct { - failing bool -} - -func (t *firstFailingTransport) RoundTrip(r *http.Request) (*http.Response, error) { - if t.failing { - t.failing = false - return nil, errors.New("Oops this failed") - } - return &http.Response{ - Status: "200 OK", - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), - Header: http.Header{ - "X-Amzn-Requestid": []string{requestID}, - }, - }, nil -} - -func TestRetrySend(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - cfg := newConfig(false) - cfg.HTTPClient = &http.Client{ - Transport: &firstFailingTransport{failing: true}, - } - - client := lambda.New(cfg) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: []float64{2}}, - {Name: "External/allOther", Scope: "", Forced: true, Data: []float64{2}}, - {Name: "External/lambda.us-west-2.amazonaws.com/all", Scope: "", Forced: false, Data: []float64{2}}, - {Name: "External/lambda.us-west-2.amazonaws.com/http/POST", Scope: "OtherTransaction/Go/" + txnName, Forced: false, Data: []float64{2}}, - {Name: "OtherTransaction/Go/" + txnName, Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/" + txnName, Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpanNoRequestID, externalSpan}) -} - -func TestRequestSentTwice(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - client := lambda.New(newConfig(false)) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - - _, firstErr := req.Send(ctx) - if nil != firstErr { - t.Error(firstErr) - } - - _, secondErr := req.Send(ctx) - if nil != secondErr { - t.Error(secondErr) - } - - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: []float64{2}}, - {Name: "External/allOther", Scope: "", Forced: true, Data: []float64{2}}, - {Name: "External/lambda.us-west-2.amazonaws.com/all", Scope: "", Forced: false, Data: []float64{2}}, - {Name: "External/lambda.us-west-2.amazonaws.com/http/POST", Scope: "OtherTransaction/Go/" + txnName, Forced: false, Data: []float64{2}}, - {Name: "OtherTransaction/Go/" + txnName, Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/" + txnName, Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpan, externalSpan}) -} - -type noRequestIDTransport struct{} - -func (t *noRequestIDTransport) RoundTrip(r *http.Request) (*http.Response, error) { - return &http.Response{ - Status: "200 OK", - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), - }, nil -} - -func TestNoRequestIDFound(t *testing.T) { - app := testApp() - txn := app.StartTransaction(txnName, nil, nil) - - cfg := newConfig(false) - cfg.HTTPClient = &http.Client{ - Transport: &noRequestIDTransport{}, - } - - client := lambda.New(cfg) - input := &lambda.InvokeInput{ - ClientContext: aws.String("MyApp"), - FunctionName: aws.String("non-existent-function"), - InvocationType: lambda.InvocationTypeEvent, - LogType: lambda.LogTypeTail, - Payload: []byte("{}"), - } - req := client.InvokeRequest(input) - InstrumentHandlers(&req.Handlers) - ctx := newrelic.NewContext(req.Context(), txn) - - _, err := req.Send(ctx) - if nil != err { - t.Error(err) - } - - txn.End() - - app.ExpectMetrics(t, externalMetrics) - app.ExpectSpanEvents(t, []internal.WantEvent{ - genericSpan, externalSpanNoRequestID}) -} diff --git a/_integrations/nrb3/README.md b/_integrations/nrb3/README.md deleted file mode 100644 index 92f200e3b..000000000 --- a/_integrations/nrb3/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrb3 [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrb3?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrb3) - -Package `nrb3` supports adding B3 headers to outgoing requests. - -```go -import "github.com/newrelic/go-agent/_integrations/nrb3" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrb3). diff --git a/_integrations/nrb3/example_test.go b/_integrations/nrb3/example_test.go deleted file mode 100644 index 0ac2941d5..000000000 --- a/_integrations/nrb3/example_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrb3 - -import ( - "fmt" - "log" - "net/http" - "os" - - newrelic "github.com/newrelic/go-agent" - "github.com/openzipkin/zipkin-go" - reporterhttp "github.com/openzipkin/zipkin-go/reporter/http" -) - -func currentTxn() newrelic.Transaction { - return nil -} - -func ExampleNewRoundTripper() { - // When defining the client, set the Transport to the NewRoundTripper. This - // will create ExternalSegments and add B3 headers for each request. - client := &http.Client{ - Transport: NewRoundTripper(nil), - } - - // Distributed Tracing must be enabled for this application. - txn := currentTxn() - - req, err := http.NewRequest("GET", "http://example.com", nil) - if nil != err { - log.Fatalln(err) - } - - // Be sure to add the transaction to the request context. This step is - // required. - req = newrelic.RequestWithTransactionContext(req, txn) - resp, err := client.Do(req) - if nil != err { - log.Fatalln(err) - } - - defer resp.Body.Close() - fmt.Println(resp.StatusCode) -} - -// This example demonstrates how to create a Zipkin reporter using the standard -// Zipkin http reporter -// (https://godoc.org/github.com/openzipkin/zipkin-go/reporter/http) to send -// Span data to New Relic. Follow this example when your application uses -// Zipkin for tracing (instead of the New Relic Go Agent) and you wish to send -// span data to the New Relic backend. The example assumes you have the -// environment variable NEW_RELIC_API_KEY set to your New Relic Insights Insert -// Key. -func Example_zipkinReporter() { - // import ( - // reporterhttp "github.com/openzipkin/zipkin-go/reporter/http" - // ) - reporter := reporterhttp.NewReporter( - "https://trace-api.newrelic.com/trace/v1", - reporterhttp.RequestCallback(func(req *http.Request) { - req.Header.Add("X-Insert-Key", os.Getenv("NEW_RELIC_API_KEY")) - req.Header.Add("Data-Format", "zipkin") - req.Header.Add("Data-Format-Version", "2") - }), - ) - defer reporter.Close() - - // use the reporter to create a new tracer - zipkin.NewTracer(reporter) -} diff --git a/_integrations/nrb3/nrb3.go b/_integrations/nrb3/nrb3.go deleted file mode 100644 index 1f4e41625..000000000 --- a/_integrations/nrb3/nrb3.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrb3 - -import ( - "net/http" - "time" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" -) - -func init() { internal.TrackUsage("integration", "b3") } - -// NewRoundTripper creates an `http.RoundTripper` to instrument external -// requests. The RoundTripper returned creates an external segment and adds B3 -// tracing headers to each request if and only if a `newrelic.Transaction` -// (https://godoc.org/github.com/newrelic/go-agent#Transaction) is found in the -// `http.Request`'s context. It then delegates to the original RoundTripper -// provided (or http.DefaultTransport if none is provided). -func NewRoundTripper(original http.RoundTripper) http.RoundTripper { - if nil == original { - original = http.DefaultTransport - } - return &b3Transport{ - idGen: internal.NewTraceIDGenerator(int64(time.Now().UnixNano())), - original: original, - } -} - -// cloneRequest mimics implementation of -// https://godoc.org/github.com/google/go-github/github#BasicAuthTransport.RoundTrip -func cloneRequest(r *http.Request) *http.Request { - // shallow copy of the struct - r2 := new(http.Request) - *r2 = *r - // deep copy of the Header - r2.Header = make(http.Header, len(r.Header)) - for k, s := range r.Header { - r2.Header[k] = append([]string(nil), s...) - } - return r2 -} - -type b3Transport struct { - idGen *internal.TraceIDGenerator - original http.RoundTripper -} - -func txnSampled(txn newrelic.Transaction) string { - if txn.IsSampled() { - return "1" - } - return "0" -} - -func addHeader(request *http.Request, key, val string) { - if val != "" { - request.Header.Add(key, val) - } -} - -func (t *b3Transport) RoundTrip(request *http.Request) (*http.Response, error) { - if txn := newrelic.FromContext(request.Context()); nil != txn { - // The specification of http.RoundTripper requires that the request is never modified. - request = cloneRequest(request) - segment := &newrelic.ExternalSegment{ - StartTime: newrelic.StartSegmentNow(txn), - Request: request, - } - defer segment.End() - - md := txn.GetTraceMetadata() - addHeader(request, "X-B3-TraceId", md.TraceID) - addHeader(request, "X-B3-SpanId", t.idGen.GenerateTraceID()) - addHeader(request, "X-B3-ParentSpanId", md.SpanID) - addHeader(request, "X-B3-Sampled", txnSampled(txn)) - } - - return t.original.RoundTrip(request) -} diff --git a/_integrations/nrb3/nrb3_doc.go b/_integrations/nrb3/nrb3_doc.go deleted file mode 100644 index 94fa41a10..000000000 --- a/_integrations/nrb3/nrb3_doc.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrb3 supports adding B3 headers to outgoing requests. -// -// When using the New Relic Go Agent, use this package if you want to add B3 -// headers ("X-B3-TraceId", etc., see -// https://github.com/openzipkin/b3-propagation) to outgoing requests. -// -// Distributed tracing must be enabled -// (https://docs.newrelic.com/docs/understand-dependencies/distributed-tracing/enable-configure/enable-distributed-tracing) -// for B3 headers to be added properly. -package nrb3 diff --git a/_integrations/nrb3/nrb3_test.go b/_integrations/nrb3/nrb3_test.go deleted file mode 100644 index aa498f63e..000000000 --- a/_integrations/nrb3/nrb3_test.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrb3 - -import ( - "net/http" - "testing" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -func TestNewRoundTripperNil(t *testing.T) { - rt := NewRoundTripper(nil) - if orig := rt.(*b3Transport).original; orig != http.DefaultTransport { - t.Error("original is not as expected:", orig) - } -} - -type roundTripperFn func(*http.Request) (*http.Response, error) - -func (fn roundTripperFn) RoundTrip(r *http.Request) (*http.Response, error) { return fn(r) } - -func TestRoundTripperNoTxn(t *testing.T) { - app := integrationsupport.NewTestApp(nil, integrationsupport.DTEnabledCfgFn) - txn := app.StartTransaction("test", nil, nil) - - var count int - rt := NewRoundTripper(roundTripperFn(func(req *http.Request) (*http.Response, error) { - count++ - return &http.Response{ - StatusCode: 200, - }, nil - })) - client := &http.Client{Transport: rt} - - req, err := http.NewRequest("GET", "http://example.com", nil) - if nil != err { - t.Fatal(err) - } - _, err = client.Do(req) - if nil != err { - t.Fatal(err) - } - txn.End() - - if count != 1 { - t.Error("incorrect call count to RoundTripper:", count) - } - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/test", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/test", Scope: "", Forced: false, Data: nil}, - }) -} - -func TestRoundTripperWithTxnSampled(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(123) - } - app := integrationsupport.NewTestApp(replyfn, integrationsupport.DTEnabledCfgFn) - txn := app.StartTransaction("test", nil, nil) - - var count int - var sent *http.Request - rt := NewRoundTripper(roundTripperFn(func(req *http.Request) (*http.Response, error) { - count++ - sent = req - return &http.Response{ - StatusCode: 200, - }, nil - })) - rt.(*b3Transport).idGen = internal.NewTraceIDGenerator(456) - client := &http.Client{Transport: rt} - - req, err := http.NewRequest("GET", "http://example.com", nil) - if nil != err { - t.Fatal(err) - } - req = newrelic.RequestWithTransactionContext(req, txn) - _, err = client.Do(req) - if nil != err { - t.Fatal(err) - } - txn.End() - - if count != 1 { - t.Error("incorrect call count to RoundTripper:", count) - } - // original request is not modified - if hdr := req.Header.Get("X-B3-TraceId"); hdr != "" { - t.Error("original request was modified, X-B3-TraceId header set:", hdr) - } - // b3 headers added - if hdr := sent.Header.Get("X-B3-TraceId"); hdr != "94d1331706b6a2b3" { - t.Error("unexpected value for X-B3-TraceId header:", hdr) - } - if hdr := sent.Header.Get("X-B3-SpanId"); hdr != "5a4f2d1b7f0cf06d" { - t.Error("unexpected value for X-B3-SpanId header:", hdr) - } - if hdr := sent.Header.Get("X-B3-ParentSpanId"); hdr != "3ffe00369da8a3b6" { - t.Error("unexpected value for X-B3-ParentSpanId header:", hdr) - } - if hdr := sent.Header.Get("X-B3-Sampled"); hdr != "1" { - t.Error("unexpected value for X-B3-Sampled header:", hdr) - } - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/example.com/http/GET", Scope: "OtherTransaction/Go/test", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/test", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/test", Scope: "", Forced: false, Data: nil}, - }) -} - -func TestRoundTripperWithTxnNotSampled(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleNothing{} - } - app := integrationsupport.NewTestApp(replyfn, integrationsupport.DTEnabledCfgFn) - txn := app.StartTransaction("test", nil, nil) - - var sent *http.Request - rt := NewRoundTripper(roundTripperFn(func(req *http.Request) (*http.Response, error) { - sent = req - return &http.Response{ - StatusCode: 200, - }, nil - })) - client := &http.Client{Transport: rt} - - req, err := http.NewRequest("GET", "http://example.com", nil) - if nil != err { - t.Fatal(err) - } - req = newrelic.RequestWithTransactionContext(req, txn) - _, err = client.Do(req) - if nil != err { - t.Fatal(err) - } - txn.End() - - if hdr := sent.Header.Get("X-B3-Sampled"); hdr != "0" { - t.Error("unexpected value for X-B3-Sampled header:", hdr) - } -} diff --git a/_integrations/nrecho/README.md b/_integrations/nrecho/README.md deleted file mode 100644 index ee8cab3c7..000000000 --- a/_integrations/nrecho/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrecho [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrecho?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrecho) - -Package `nrecho` instruments https://github.com/labstack/echo applications. - -```go -import "github.com/newrelic/go-agent/_integrations/nrecho" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrecho). diff --git a/_integrations/nrecho/example/main.go b/_integrations/nrecho/example/main.go deleted file mode 100644 index eb11b726b..000000000 --- a/_integrations/nrecho/example/main.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "net/http" - "os" - - "github.com/labstack/echo" - "github.com/labstack/echo/middleware" - "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrecho" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func getUser(c echo.Context) error { - id := c.Param("id") - - if txn := nrecho.FromContext(c); nil != txn { - txn.AddAttribute("userId", id) - } - - return c.String(http.StatusOK, id) -} - -func main() { - cfg := newrelic.NewConfig("Echo App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - // Echo instance - e := echo.New() - - // The New Relic Middleware should be the first middleware registered - e.Use(nrecho.Middleware(app)) - - // Routes - e.GET("/home", func(c echo.Context) error { - return c.String(http.StatusOK, "Hello, World!") - }) - - // Groups - g := e.Group("/user") - g.Use(middleware.Gzip()) - g.GET("/:id", getUser) - - // Start server - e.Start(":8000") -} diff --git a/_integrations/nrecho/nrecho.go b/_integrations/nrecho/nrecho.go deleted file mode 100644 index 15171d425..000000000 --- a/_integrations/nrecho/nrecho.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrecho instruments https://github.com/labstack/echo applications. -// -// Use this package to instrument inbound requests handled by an echo.Echo -// instance. -// -// e := echo.New() -// // Add the nrecho middleware before other middlewares or routes: -// e.Use(nrecho.Middleware(app)) -// -// Example: https://github.com/newrelic/go-agent/tree/master/_integrations/nrecho/example/main.go -package nrecho - -import ( - "net/http" - "reflect" - - "github.com/labstack/echo" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" -) - -func init() { internal.TrackUsage("integration", "framework", "echo") } - -// FromContext returns the Transaction from the context if present, and nil -// otherwise. -func FromContext(c echo.Context) newrelic.Transaction { - return newrelic.FromContext(c.Request().Context()) -} - -func handlerPointer(handler echo.HandlerFunc) uintptr { - return reflect.ValueOf(handler).Pointer() -} - -func transactionName(c echo.Context) string { - ptr := handlerPointer(c.Handler()) - if ptr == handlerPointer(echo.NotFoundHandler) { - return "NotFoundHandler" - } - if ptr == handlerPointer(echo.MethodNotAllowedHandler) { - return "MethodNotAllowedHandler" - } - return c.Path() -} - -// Middleware creates Echo middleware that instruments requests. -// -// e := echo.New() -// // Add the nrecho middleware before other middlewares or routes: -// e.Use(nrecho.Middleware(app)) -// -func Middleware(app newrelic.Application) func(echo.HandlerFunc) echo.HandlerFunc { - - if nil == app { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return next - } - } - - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) (err error) { - rw := c.Response().Writer - txn := app.StartTransaction(transactionName(c), rw, c.Request()) - defer txn.End() - - c.Response().Writer = txn - - // Add txn to c.Request().Context() - c.SetRequest(c.Request().WithContext(newrelic.NewContext(c.Request().Context(), txn))) - - err = next(c) - - // Record the response code. The response headers are not captured - // in this case because they are set after this middleware returns. - // Designed to mimic the logic in echo.DefaultHTTPErrorHandler. - if nil != err && !c.Response().Committed { - - txn.SetWebResponse(nil) - c.Response().Writer = rw - - if httperr, ok := err.(*echo.HTTPError); ok { - txn.WriteHeader(httperr.Code) - } else { - txn.WriteHeader(http.StatusInternalServerError) - } - } - - return - } - } -} diff --git a/_integrations/nrecho/nrecho_test.go b/_integrations/nrecho/nrecho_test.go deleted file mode 100644 index 86d29be41..000000000 --- a/_integrations/nrecho/nrecho_test.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrecho - -import ( - "errors" - "net/http" - "net/http/httptest" - "testing" - - "github.com/labstack/echo" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -func TestBasicRoute(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - - e := echo.New() - e.Use(Middleware(app)) - e.GET("/hello", func(c echo.Context) error { - return c.Blob(http.StatusOK, "text/html", []byte("Hello, World!")) - }) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello?remove=me", nil) - if err != nil { - t.Fatal(err) - } - - e.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "Hello, World!" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "hello", - IsWeb: true, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "S", - }, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": "200", - "request.method": "GET", - "response.headers.contentType": "text/html", - "request.uri": "/hello", - }, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestNilApp(t *testing.T) { - e := echo.New() - e.Use(Middleware(nil)) - e.GET("/hello", func(c echo.Context) error { - return c.String(http.StatusOK, "Hello, World!") - }) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello?remove=me", nil) - if err != nil { - t.Fatal(err) - } - - e.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "Hello, World!" { - t.Error("wrong response body", respBody) - } -} - -func TestTransactionContext(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - - e := echo.New() - e.Use(Middleware(app)) - e.GET("/hello", func(c echo.Context) error { - txn := FromContext(c) - if nil != txn { - txn.NoticeError(errors.New("ooops")) - } - return c.String(http.StatusOK, "Hello, World!") - }) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello?remove=me", nil) - if err != nil { - t.Fatal(err) - } - - e.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "Hello, World!" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "hello", - IsWeb: true, - NumErrors: 1, - }) -} - -func TestNotFoundHandler(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - - e := echo.New() - e.Use(Middleware(app)) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello?remove=me", nil) - if err != nil { - t.Fatal(err) - } - - e.ServeHTTP(response, req) - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "NotFoundHandler", - IsWeb: true, - }) -} - -func TestMethodNotAllowedHandler(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - - e := echo.New() - e.Use(Middleware(app)) - e.GET("/hello", func(c echo.Context) error { - return c.String(http.StatusOK, "Hello, World!") - }) - - response := httptest.NewRecorder() - req, err := http.NewRequest("POST", "/hello?remove=me", nil) - if err != nil { - t.Fatal(err) - } - - e.ServeHTTP(response, req) - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "MethodNotAllowedHandler", - IsWeb: true, - NumErrors: 1, - }) -} - -func TestReturnsHTTPError(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - - e := echo.New() - e.Use(Middleware(app)) - e.GET("/hello", func(c echo.Context) error { - return echo.NewHTTPError(http.StatusTeapot, "I'm a teapot!") - }) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello?remove=me", nil) - if err != nil { - t.Fatal(err) - } - - e.ServeHTTP(response, req) - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "hello", - IsWeb: true, - NumErrors: 1, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": "418", - "request.method": "GET", - "request.uri": "/hello", - }, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestReturnsError(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - - e := echo.New() - e.Use(Middleware(app)) - e.GET("/hello", func(c echo.Context) error { - return errors.New("ooooooooops") - }) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello?remove=me", nil) - if err != nil { - t.Fatal(err) - } - - e.ServeHTTP(response, req) - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "hello", - IsWeb: true, - NumErrors: 1, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": "500", - "request.method": "GET", - "request.uri": "/hello", - }, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestResponseCode(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - - e := echo.New() - e.Use(Middleware(app)) - e.GET("/hello", func(c echo.Context) error { - return c.Blob(http.StatusTeapot, "text/html", []byte("Hello, World!")) - }) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello?remove=me", nil) - if err != nil { - t.Fatal(err) - } - - e.ServeHTTP(response, req) - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "hello", - IsWeb: true, - NumErrors: 1, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": "418", - "request.method": "GET", - "response.headers.contentType": "text/html", - "request.uri": "/hello", - }, - UserAttributes: map[string]interface{}{}, - }}) -} diff --git a/_integrations/nrgin/v1/README.md b/_integrations/nrgin/v1/README.md deleted file mode 100644 index f7e561669..000000000 --- a/_integrations/nrgin/v1/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrgin/v1 [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrgin/v1?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrgin/v1) - -Package `nrgin` instruments https://github.com/gin-gonic/gin applications. - -```go -import "github.com/newrelic/go-agent/_integrations/nrgin/v1" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrgin/v1). diff --git a/_integrations/nrgin/v1/example/main.go b/_integrations/nrgin/v1/example/main.go deleted file mode 100644 index cc4169482..000000000 --- a/_integrations/nrgin/v1/example/main.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "os" - - "github.com/gin-gonic/gin" - "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrgin/v1" -) - -func makeGinEndpoint(s string) func(*gin.Context) { - return func(c *gin.Context) { - c.Writer.WriteString(s) - } -} - -func v1login(c *gin.Context) { c.Writer.WriteString("v1 login") } -func v1submit(c *gin.Context) { c.Writer.WriteString("v1 submit") } -func v1read(c *gin.Context) { c.Writer.WriteString("v1 read") } - -func endpoint404(c *gin.Context) { - c.Writer.WriteHeader(404) - c.Writer.WriteString("returning 404") -} - -func endpointChangeCode(c *gin.Context) { - // gin.ResponseWriter buffers the response code so that it can be - // changed before the first write. - c.Writer.WriteHeader(404) - c.Writer.WriteHeader(200) - c.Writer.WriteString("actually ok!") -} - -func endpointResponseHeaders(c *gin.Context) { - // Since gin.ResponseWriter buffers the response code, response headers - // can be set afterwards. - c.Writer.WriteHeader(200) - c.Writer.Header().Set("Content-Type", "application/json") - c.Writer.WriteString(`{"zip":"zap"}`) -} - -func endpointNotFound(c *gin.Context) { - c.Writer.WriteString("there's no endpoint for that!") -} - -func endpointAccessTransaction(c *gin.Context) { - if txn := nrgin.Transaction(c); nil != txn { - txn.SetName("custom-name") - } - c.Writer.WriteString("changed the name of the transaction!") -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("Gin App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - router := gin.Default() - router.Use(nrgin.Middleware(app)) - - router.GET("/404", endpoint404) - router.GET("/change", endpointChangeCode) - router.GET("/headers", endpointResponseHeaders) - router.GET("/txn", endpointAccessTransaction) - - // Since the handler function name is used as the transaction name, - // anonymous functions do not get usefully named. We encourage - // transforming anonymous functions into named functions. - router.GET("/anon", func(c *gin.Context) { - c.Writer.WriteString("anonymous function handler") - }) - - v1 := router.Group("/v1") - v1.GET("/login", v1login) - v1.GET("/submit", v1submit) - v1.GET("/read", v1read) - - router.NoRoute(endpointNotFound) - - router.Run(":8000") -} diff --git a/_integrations/nrgin/v1/nrgin.go b/_integrations/nrgin/v1/nrgin.go deleted file mode 100644 index e52fa4469..000000000 --- a/_integrations/nrgin/v1/nrgin.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrgin instruments https://github.com/gin-gonic/gin applications. -// -// Use this package to instrument inbound requests handled by a gin.Engine. -// Call nrgin.Middleware to get a gin.HandlerFunc which can be added to your -// application as a middleware: -// -// router := gin.Default() -// // Add the nrgin middleware before other middlewares or routes: -// router.Use(nrgin.Middleware(app)) -// -// Example: https://github.com/newrelic/go-agent/tree/master/_integrations/nrgin/v1/example/main.go -package nrgin - -import ( - "net/http" - - "github.com/gin-gonic/gin" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" -) - -func init() { internal.TrackUsage("integration", "framework", "gin", "v1") } - -// headerResponseWriter gives the transaction access to response headers and the -// response code. -type headerResponseWriter struct{ w gin.ResponseWriter } - -func (w *headerResponseWriter) Header() http.Header { return w.w.Header() } -func (w *headerResponseWriter) Write([]byte) (int, error) { return 0, nil } -func (w *headerResponseWriter) WriteHeader(int) {} - -var _ http.ResponseWriter = &headerResponseWriter{} - -// replacementResponseWriter mimics the behavior of gin.ResponseWriter which -// buffers the response code rather than writing it when -// gin.ResponseWriter.WriteHeader is called. -type replacementResponseWriter struct { - gin.ResponseWriter - txn newrelic.Transaction - code int - written bool -} - -var _ gin.ResponseWriter = &replacementResponseWriter{} - -func (w *replacementResponseWriter) flushHeader() { - if !w.written { - w.txn.WriteHeader(w.code) - w.written = true - } -} - -func (w *replacementResponseWriter) WriteHeader(code int) { - w.code = code - w.ResponseWriter.WriteHeader(code) -} - -func (w *replacementResponseWriter) Write(data []byte) (int, error) { - w.flushHeader() - return w.ResponseWriter.Write(data) -} - -func (w *replacementResponseWriter) WriteString(s string) (int, error) { - w.flushHeader() - return w.ResponseWriter.WriteString(s) -} - -func (w *replacementResponseWriter) WriteHeaderNow() { - w.flushHeader() - w.ResponseWriter.WriteHeaderNow() -} - -// Context avoids making this package 1.7+ specific. -type Context interface { - Value(key interface{}) interface{} -} - -// Transaction returns the transaction stored inside the context, or nil if not -// found. -func Transaction(c Context) newrelic.Transaction { - if v := c.Value(internal.GinTransactionContextKey); nil != v { - if txn, ok := v.(newrelic.Transaction); ok { - return txn - } - } - if v := c.Value(internal.TransactionContextKey); nil != v { - if txn, ok := v.(newrelic.Transaction); ok { - return txn - } - } - return nil -} - -// Middleware creates a Gin middleware that instruments requests. -// -// router := gin.Default() -// // Add the nrgin middleware before other middlewares or routes: -// router.Use(nrgin.Middleware(app)) -// -func Middleware(app newrelic.Application) gin.HandlerFunc { - return func(c *gin.Context) { - if app != nil { - name := c.HandlerName() - w := &headerResponseWriter{w: c.Writer} - txn := app.StartTransaction(name, w, c.Request) - defer txn.End() - - repl := &replacementResponseWriter{ - ResponseWriter: c.Writer, - txn: txn, - code: http.StatusOK, - } - c.Writer = repl - defer repl.flushHeader() - - c.Set(internal.GinTransactionContextKey, txn) - } - c.Next() - } -} diff --git a/_integrations/nrgin/v1/nrgin_context_test.go b/_integrations/nrgin/v1/nrgin_context_test.go deleted file mode 100644 index 5d27ae864..000000000 --- a/_integrations/nrgin/v1/nrgin_context_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.7 - -package nrgin - -import ( - "context" - "errors" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -func accessTransactionContextContext(c *gin.Context) { - var ctx context.Context = c - // Transaction is designed to take both a context.Context and a - // *gin.Context. - if txn := Transaction(ctx); nil != txn { - txn.NoticeError(errors.New("problem")) - } - c.Writer.WriteString("accessTransactionContextContext") -} - -func TestContextContextTransaction(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := gin.Default() - router.Use(Middleware(app)) - router.GET("/txn", accessTransactionContextContext) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/txn", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "accessTransactionContextContext" { - t.Error("wrong response body", respBody) - } - if response.Code != 200 { - t.Error("wrong response code", response.Code) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: pkg + ".accessTransactionContextContext", - IsWeb: true, - NumErrors: 1, - }) -} - -func accessTransactionFromContext(c *gin.Context) { - // This tests that FromContext will find the transaction added to a - // *gin.Context and by nrgin.Middleware. - if txn := newrelic.FromContext(c); nil != txn { - txn.NoticeError(errors.New("problem")) - } - c.Writer.WriteString("accessTransactionFromContext") -} - -func TestFromContext(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := gin.Default() - router.Use(Middleware(app)) - router.GET("/txn", accessTransactionFromContext) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/txn", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "accessTransactionFromContext" { - t.Error("wrong response body", respBody) - } - if response.Code != 200 { - t.Error("wrong response code", response.Code) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: pkg + ".accessTransactionFromContext", - IsWeb: true, - NumErrors: 1, - }) -} - -func TestContextWithoutTransaction(t *testing.T) { - txn := Transaction(context.Background()) - if txn != nil { - t.Error("didn't expect a transaction", txn) - } - ctx := context.WithValue(context.Background(), internal.TransactionContextKey, 123) - txn = Transaction(ctx) - if txn != nil { - t.Error("didn't expect a transaction", txn) - } -} - -func TestNewContextTransaction(t *testing.T) { - // This tests that nrgin.Transaction will find a transaction added to - // to a context using newrelic.NewContext. - app := integrationsupport.NewBasicTestApp() - txn := app.StartTransaction("name", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - if tx := Transaction(ctx); nil != tx { - tx.NoticeError(errors.New("problem")) - } - txn.End() - - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "name", - IsWeb: false, - NumErrors: 1, - }) -} diff --git a/_integrations/nrgin/v1/nrgin_test.go b/_integrations/nrgin/v1/nrgin_test.go deleted file mode 100644 index dae9c9629..000000000 --- a/_integrations/nrgin/v1/nrgin_test.go +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrgin - -import ( - "errors" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -var ( - pkg = "github.com/newrelic/go-agent/_integrations/nrgin/v1" -) - -func hello(c *gin.Context) { - c.Writer.WriteString("hello response") -} - -func TestBasicRoute(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := gin.Default() - router.Use(Middleware(app)) - router.GET("/hello", hello) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "hello response" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: pkg + ".hello", - IsWeb: true, - }) -} - -func TestRouterGroup(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := gin.Default() - router.Use(Middleware(app)) - group := router.Group("/group") - group.GET("/hello", hello) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/group/hello", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "hello response" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: pkg + ".hello", - IsWeb: true, - }) -} - -func TestAnonymousHandler(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := gin.Default() - router.Use(Middleware(app)) - router.GET("/anon", func(c *gin.Context) { - c.Writer.WriteString("anonymous function handler") - }) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/anon", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "anonymous function handler" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: pkg + ".TestAnonymousHandler.func1", - IsWeb: true, - }) -} - -func multipleWriteHeader(c *gin.Context) { - // Unlike http.ResponseWriter, gin.ResponseWriter does not immediately - // write the first WriteHeader. Instead, it gets buffered until the - // first Write call. - c.Writer.WriteHeader(200) - c.Writer.WriteHeader(500) - c.Writer.WriteString("multipleWriteHeader") -} - -func TestMultipleWriteHeader(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := gin.Default() - router.Use(Middleware(app)) - router.GET("/header", multipleWriteHeader) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/header", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "multipleWriteHeader" { - t.Error("wrong response body", respBody) - } - if response.Code != 500 { - t.Error("wrong response code", response.Code) - } - // Error metrics test the 500 response code capture. - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: pkg + ".multipleWriteHeader", - IsWeb: true, - NumErrors: 1, - }) -} - -func accessTransactionGinContext(c *gin.Context) { - if txn := Transaction(c); nil != txn { - txn.NoticeError(errors.New("problem")) - } - c.Writer.WriteString("accessTransactionGinContext") -} - -func TestContextTransaction(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := gin.Default() - router.Use(Middleware(app)) - router.GET("/txn", accessTransactionGinContext) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/txn", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "accessTransactionGinContext" { - t.Error("wrong response body", respBody) - } - if response.Code != 200 { - t.Error("wrong response code", response.Code) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: pkg + ".accessTransactionGinContext", - IsWeb: true, - NumErrors: 1, - }) -} - -func TestNilApp(t *testing.T) { - var app newrelic.Application - router := gin.Default() - router.Use(Middleware(app)) - router.GET("/hello", hello) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "hello response" { - t.Error("wrong response body", respBody) - } -} - -func errorStatus(c *gin.Context) { - c.String(500, "an error happened") -} - -func TestStatusCodes(t *testing.T) { - // Test that we are correctly able to collect status code. - // This behavior changed with this pull request: https://github.com/gin-gonic/gin/pull/1606 - // In Gin v1.4.0 and below, we always recorded a 200 status, whereas with - // newer Gin versions we now correctly capture the status. - app := integrationsupport.NewBasicTestApp() - router := gin.Default() - router.Use(Middleware(app)) - router.GET("/err", errorStatus) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/err", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "an error happened" { - t.Error("wrong response body", respBody) - } - if response.Code != 500 { - t.Error("wrong response code", response.Code) - } - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/" + pkg + ".errorStatus", - "nr.apdexPerfZone": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 500, - "request.method": "GET", - "request.uri": "/err", - "response.headers.contentType": "text/plain; charset=utf-8", - }, - }}) -} - -func noBody(c *gin.Context) { - c.Status(500) -} - -func TestNoResponseBody(t *testing.T) { - // Test that when no response body is sent (i.e. c.Writer.Write is never - // called) that we still capture status code. - app := integrationsupport.NewBasicTestApp() - router := gin.Default() - router.Use(Middleware(app)) - router.GET("/nobody", noBody) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/nobody", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "" { - t.Error("wrong response body", respBody) - } - if response.Code != 500 { - t.Error("wrong response code", response.Code) - } - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/" + pkg + ".noBody", - "nr.apdexPerfZone": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 500, - "request.method": "GET", - "request.uri": "/nobody", - }, - }}) -} diff --git a/_integrations/nrgorilla/v1/README.md b/_integrations/nrgorilla/v1/README.md deleted file mode 100644 index b87202920..000000000 --- a/_integrations/nrgorilla/v1/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrgorilla/v1 [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrgorilla/v1?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrgorilla/v1) - -Package `nrgorilla` instruments https://github.com/gorilla/mux applications. - -```go -import "github.com/newrelic/go-agent/_integrations/nrgorilla/v1" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrgorilla/v1). diff --git a/_integrations/nrgorilla/v1/example/main.go b/_integrations/nrgorilla/v1/example/main.go deleted file mode 100644 index afe2f2b44..000000000 --- a/_integrations/nrgorilla/v1/example/main.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "net/http" - "os" - - "github.com/gorilla/mux" - newrelic "github.com/newrelic/go-agent" - nrgorilla "github.com/newrelic/go-agent/_integrations/nrgorilla/v1" -) - -func makeHandler(text string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(text)) - }) -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("Gorilla App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - r := mux.NewRouter() - r.Handle("/", makeHandler("index")) - r.Handle("/alpha", makeHandler("alpha")) - - users := r.PathPrefix("/users").Subrouter() - users.Handle("/add", makeHandler("adding user")) - users.Handle("/delete", makeHandler("deleting user")) - - // The route name will be used as the transaction name if one is set. - r.Handle("/named", makeHandler("named route")).Name("special-name-route") - - // The NotFoundHandler will be instrumented if it is set. - r.NotFoundHandler = makeHandler("not found") - - http.ListenAndServe(":8000", nrgorilla.InstrumentRoutes(r, app)) -} diff --git a/_integrations/nrgorilla/v1/nrgorilla.go b/_integrations/nrgorilla/v1/nrgorilla.go deleted file mode 100644 index 3050eb8ed..000000000 --- a/_integrations/nrgorilla/v1/nrgorilla.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrgorilla instruments https://github.com/gorilla/mux applications. -// -// Use this package to instrument inbound requests handled by a gorilla -// mux.Router. Call nrgorilla.InstrumentRoutes on your gorilla mux.Router -// after your routes have been added to it. -// -// Example: https://github.com/newrelic/go-agent/tree/master/_integrations/nrgorilla/v1/example/main.go -package nrgorilla - -import ( - "net/http" - - "github.com/gorilla/mux" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" -) - -func init() { internal.TrackUsage("integration", "framework", "gorilla", "v1") } - -type instrumentedHandler struct { - name string - app newrelic.Application - orig http.Handler -} - -func (h instrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - txn := h.app.StartTransaction(h.name, w, r) - defer txn.End() - - r = newrelic.RequestWithTransactionContext(r, txn) - - h.orig.ServeHTTP(txn, r) -} - -func instrumentRoute(h http.Handler, app newrelic.Application, name string) http.Handler { - if _, ok := h.(instrumentedHandler); ok { - return h - } - return instrumentedHandler{ - name: name, - orig: h, - app: app, - } -} - -func routeName(route *mux.Route) string { - if nil == route { - return "" - } - if n := route.GetName(); n != "" { - return n - } - if n, _ := route.GetPathTemplate(); n != "" { - return n - } - n, _ := route.GetHostTemplate() - return n -} - -// InstrumentRoutes instruments requests through the provided mux.Router. Use -// this after the routes have been added to the router. -func InstrumentRoutes(r *mux.Router, app newrelic.Application) *mux.Router { - if app != nil { - r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { - h := instrumentRoute(route.GetHandler(), app, routeName(route)) - route.Handler(h) - return nil - }) - if nil != r.NotFoundHandler { - r.NotFoundHandler = instrumentRoute(r.NotFoundHandler, app, "NotFoundHandler") - } - } - return r -} diff --git a/_integrations/nrgorilla/v1/nrgorilla_test.go b/_integrations/nrgorilla/v1/nrgorilla_test.go deleted file mode 100644 index afaeb40fb..000000000 --- a/_integrations/nrgorilla/v1/nrgorilla_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrgorilla - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/gorilla/mux" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -func makeHandler(text string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(text)) - }) -} - -func TestBasicRoute(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - r := mux.NewRouter() - r.Handle("/alpha", makeHandler("alpha response")) - InstrumentRoutes(r, app) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/alpha", nil) - if err != nil { - t.Fatal(err) - } - r.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "alpha response" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "alpha", - IsWeb: true, - }) -} - -func TestSubrouterRoute(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - r := mux.NewRouter() - users := r.PathPrefix("/users").Subrouter() - users.Handle("/add", makeHandler("adding user")) - InstrumentRoutes(r, app) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/users/add", nil) - if err != nil { - t.Fatal(err) - } - r.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "adding user" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "users/add", - IsWeb: true, - }) -} - -func TestNamedRoute(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - r := mux.NewRouter() - r.Handle("/named", makeHandler("named route")).Name("special-name-route") - InstrumentRoutes(r, app) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/named", nil) - if err != nil { - t.Fatal(err) - } - r.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "named route" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "special-name-route", - IsWeb: true, - }) -} - -func TestRouteNotFound(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - r := mux.NewRouter() - r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(500) - w.Write([]byte("not found")) - }) - // Tests that routes do not get double instrumented when - // InstrumentRoutes is called twice by expecting error metrics with a - // count of 1. - InstrumentRoutes(r, app) - InstrumentRoutes(r, app) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/foo", nil) - if err != nil { - t.Fatal(err) - } - r.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "not found" { - t.Error("wrong response body", respBody) - } - if response.Code != 500 { - t.Error("wrong response code", response.Code) - } - // Error metrics test the 500 response code capture. - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "NotFoundHandler", - IsWeb: true, - NumErrors: 1, - }) -} - -func TestNilApp(t *testing.T) { - var app newrelic.Application - r := mux.NewRouter() - r.Handle("/alpha", makeHandler("alpha response")) - InstrumentRoutes(r, app) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/alpha", nil) - if err != nil { - t.Fatal(err) - } - r.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "alpha response" { - t.Error("wrong response body", respBody) - } -} diff --git a/_integrations/nrgrpc/README.md b/_integrations/nrgrpc/README.md deleted file mode 100644 index 0243fc008..000000000 --- a/_integrations/nrgrpc/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrgrpc [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrgrpc?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrgrpc) - -Package `nrgrpc` instruments https://github.com/grpc/grpc-go. - -```go -import "github.com/newrelic/go-agent/_integrations/nrgrpc" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrgrpc). diff --git a/_integrations/nrgrpc/example/client/client.go b/_integrations/nrgrpc/example/client/client.go deleted file mode 100644 index 3fdff8630..000000000 --- a/_integrations/nrgrpc/example/client/client.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "fmt" - "io" - "os" - "time" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrgrpc" - sampleapp "github.com/newrelic/go-agent/_integrations/nrgrpc/example/sampleapp" - "google.golang.org/grpc" -) - -func doUnaryUnary(ctx context.Context, client sampleapp.SampleApplicationClient) { - msg, err := client.DoUnaryUnary(ctx, &sampleapp.Message{Text: "Hello DoUnaryUnary"}) - if nil != err { - panic(err) - } - fmt.Println(msg.Text) -} - -func doUnaryStream(ctx context.Context, client sampleapp.SampleApplicationClient) { - stream, err := client.DoUnaryStream(ctx, &sampleapp.Message{Text: "Hello DoUnaryStream"}) - if nil != err { - panic(err) - } - for { - msg, err := stream.Recv() - if err == io.EOF { - break - } - if nil != err { - panic(err) - } - fmt.Println(msg.Text) - } -} - -func doStreamUnary(ctx context.Context, client sampleapp.SampleApplicationClient) { - stream, err := client.DoStreamUnary(ctx) - if nil != err { - panic(err) - } - for i := 0; i < 3; i++ { - if err := stream.Send(&sampleapp.Message{Text: "Hello DoStreamUnary"}); nil != err { - if err == io.EOF { - break - } - panic(err) - } - } - msg, err := stream.CloseAndRecv() - if nil != err { - panic(err) - } - fmt.Println(msg.Text) -} - -func doStreamStream(ctx context.Context, client sampleapp.SampleApplicationClient) { - stream, err := client.DoStreamStream(ctx) - if nil != err { - panic(err) - } - waitc := make(chan struct{}) - go func() { - for { - msg, err := stream.Recv() - if err == io.EOF { - close(waitc) - return - } - if err != nil { - panic(err) - } - fmt.Println(msg.Text) - } - }() - for i := 0; i < 3; i++ { - if err := stream.Send(&sampleapp.Message{Text: "Hello DoStreamStream"}); err != nil { - panic(err) - } - } - stream.CloseSend() - <-waitc -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("gRPC Client", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - err = app.WaitForConnection(10 * time.Second) - if nil != err { - panic(err) - } - defer app.Shutdown(10 * time.Second) - - txn := app.StartTransaction("main", nil, nil) - defer txn.End() - - conn, err := grpc.Dial( - "localhost:8080", - grpc.WithInsecure(), - // Add the New Relic gRPC client instrumentation - grpc.WithUnaryInterceptor(nrgrpc.UnaryClientInterceptor), - grpc.WithStreamInterceptor(nrgrpc.StreamClientInterceptor), - ) - if err != nil { - panic(err) - } - defer conn.Close() - - client := sampleapp.NewSampleApplicationClient(conn) - ctx := newrelic.NewContext(context.Background(), txn) - - doUnaryUnary(ctx, client) - doUnaryStream(ctx, client) - doStreamUnary(ctx, client) - doStreamStream(ctx, client) -} diff --git a/_integrations/nrgrpc/example/sampleapp/sampleapp.pb.go b/_integrations/nrgrpc/example/sampleapp/sampleapp.pb.go deleted file mode 100644 index 82a38585c..000000000 --- a/_integrations/nrgrpc/example/sampleapp/sampleapp.pb.go +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: sampleapp.proto - -package sampleapp - -import ( - context "context" - fmt "fmt" - proto "github.com/golang/protobuf/proto" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package - -type Message struct { - Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Message) Reset() { *m = Message{} } -func (m *Message) String() string { return proto.CompactTextString(m) } -func (*Message) ProtoMessage() {} -func (*Message) Descriptor() ([]byte, []int) { - return fileDescriptor_38ae74b4e52ac4e0, []int{0} -} - -func (m *Message) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Message.Unmarshal(m, b) -} -func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Message.Marshal(b, m, deterministic) -} -func (m *Message) XXX_Merge(src proto.Message) { - xxx_messageInfo_Message.Merge(m, src) -} -func (m *Message) XXX_Size() int { - return xxx_messageInfo_Message.Size(m) -} -func (m *Message) XXX_DiscardUnknown() { - xxx_messageInfo_Message.DiscardUnknown(m) -} - -var xxx_messageInfo_Message proto.InternalMessageInfo - -func (m *Message) GetText() string { - if m != nil { - return m.Text - } - return "" -} - -func init() { - proto.RegisterType((*Message)(nil), "Message") -} - -func init() { proto.RegisterFile("sampleapp.proto", fileDescriptor_38ae74b4e52ac4e0) } - -var fileDescriptor_38ae74b4e52ac4e0 = []byte{ - // 153 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2f, 0x4e, 0xcc, 0x2d, - 0xc8, 0x49, 0x4d, 0x2c, 0x28, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x92, 0xe5, 0x62, 0xf7, - 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x15, 0x12, 0xe2, 0x62, 0x29, 0x49, 0xad, 0x28, 0x91, 0x60, - 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x8d, 0xb6, 0x33, 0x72, 0x09, 0x06, 0x83, 0xb5, 0x38, - 0x16, 0x14, 0xe4, 0x64, 0x26, 0x27, 0x96, 0x64, 0xe6, 0xe7, 0x09, 0xa9, 0x70, 0xf1, 0xb8, 0xe4, - 0x87, 0xe6, 0x25, 0x16, 0x55, 0x82, 0x09, 0x21, 0x0e, 0x3d, 0xa8, 0x19, 0x52, 0x70, 0x96, 0x12, - 0x83, 0x90, 0x3a, 0x17, 0x2f, 0x54, 0x55, 0x70, 0x49, 0x51, 0x6a, 0x62, 0x2e, 0x76, 0x65, 0x06, - 0x8c, 0x10, 0x85, 0x10, 0x35, 0x78, 0xcc, 0xd3, 0x60, 0x14, 0xd2, 0xe2, 0xe2, 0x83, 0x29, 0xc4, - 0x67, 0xa4, 0x06, 0xa3, 0x01, 0x63, 0x12, 0x1b, 0xd8, 0x7f, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xe8, 0x8b, 0x56, 0x80, 0xf2, 0x00, 0x00, 0x00, -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// SampleApplicationClient is the client API for SampleApplication service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type SampleApplicationClient interface { - DoUnaryUnary(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) - DoUnaryStream(ctx context.Context, in *Message, opts ...grpc.CallOption) (SampleApplication_DoUnaryStreamClient, error) - DoStreamUnary(ctx context.Context, opts ...grpc.CallOption) (SampleApplication_DoStreamUnaryClient, error) - DoStreamStream(ctx context.Context, opts ...grpc.CallOption) (SampleApplication_DoStreamStreamClient, error) -} - -type sampleApplicationClient struct { - cc *grpc.ClientConn -} - -func NewSampleApplicationClient(cc *grpc.ClientConn) SampleApplicationClient { - return &sampleApplicationClient{cc} -} - -func (c *sampleApplicationClient) DoUnaryUnary(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) { - out := new(Message) - err := c.cc.Invoke(ctx, "/SampleApplication/DoUnaryUnary", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *sampleApplicationClient) DoUnaryStream(ctx context.Context, in *Message, opts ...grpc.CallOption) (SampleApplication_DoUnaryStreamClient, error) { - stream, err := c.cc.NewStream(ctx, &_SampleApplication_serviceDesc.Streams[0], "/SampleApplication/DoUnaryStream", opts...) - if err != nil { - return nil, err - } - x := &sampleApplicationDoUnaryStreamClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type SampleApplication_DoUnaryStreamClient interface { - Recv() (*Message, error) - grpc.ClientStream -} - -type sampleApplicationDoUnaryStreamClient struct { - grpc.ClientStream -} - -func (x *sampleApplicationDoUnaryStreamClient) Recv() (*Message, error) { - m := new(Message) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *sampleApplicationClient) DoStreamUnary(ctx context.Context, opts ...grpc.CallOption) (SampleApplication_DoStreamUnaryClient, error) { - stream, err := c.cc.NewStream(ctx, &_SampleApplication_serviceDesc.Streams[1], "/SampleApplication/DoStreamUnary", opts...) - if err != nil { - return nil, err - } - x := &sampleApplicationDoStreamUnaryClient{stream} - return x, nil -} - -type SampleApplication_DoStreamUnaryClient interface { - Send(*Message) error - CloseAndRecv() (*Message, error) - grpc.ClientStream -} - -type sampleApplicationDoStreamUnaryClient struct { - grpc.ClientStream -} - -func (x *sampleApplicationDoStreamUnaryClient) Send(m *Message) error { - return x.ClientStream.SendMsg(m) -} - -func (x *sampleApplicationDoStreamUnaryClient) CloseAndRecv() (*Message, error) { - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - m := new(Message) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *sampleApplicationClient) DoStreamStream(ctx context.Context, opts ...grpc.CallOption) (SampleApplication_DoStreamStreamClient, error) { - stream, err := c.cc.NewStream(ctx, &_SampleApplication_serviceDesc.Streams[2], "/SampleApplication/DoStreamStream", opts...) - if err != nil { - return nil, err - } - x := &sampleApplicationDoStreamStreamClient{stream} - return x, nil -} - -type SampleApplication_DoStreamStreamClient interface { - Send(*Message) error - Recv() (*Message, error) - grpc.ClientStream -} - -type sampleApplicationDoStreamStreamClient struct { - grpc.ClientStream -} - -func (x *sampleApplicationDoStreamStreamClient) Send(m *Message) error { - return x.ClientStream.SendMsg(m) -} - -func (x *sampleApplicationDoStreamStreamClient) Recv() (*Message, error) { - m := new(Message) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -// SampleApplicationServer is the server API for SampleApplication service. -type SampleApplicationServer interface { - DoUnaryUnary(context.Context, *Message) (*Message, error) - DoUnaryStream(*Message, SampleApplication_DoUnaryStreamServer) error - DoStreamUnary(SampleApplication_DoStreamUnaryServer) error - DoStreamStream(SampleApplication_DoStreamStreamServer) error -} - -// UnimplementedSampleApplicationServer can be embedded to have forward compatible implementations. -type UnimplementedSampleApplicationServer struct { -} - -func (*UnimplementedSampleApplicationServer) DoUnaryUnary(ctx context.Context, req *Message) (*Message, error) { - return nil, status.Errorf(codes.Unimplemented, "method DoUnaryUnary not implemented") -} -func (*UnimplementedSampleApplicationServer) DoUnaryStream(req *Message, srv SampleApplication_DoUnaryStreamServer) error { - return status.Errorf(codes.Unimplemented, "method DoUnaryStream not implemented") -} -func (*UnimplementedSampleApplicationServer) DoStreamUnary(srv SampleApplication_DoStreamUnaryServer) error { - return status.Errorf(codes.Unimplemented, "method DoStreamUnary not implemented") -} -func (*UnimplementedSampleApplicationServer) DoStreamStream(srv SampleApplication_DoStreamStreamServer) error { - return status.Errorf(codes.Unimplemented, "method DoStreamStream not implemented") -} - -func RegisterSampleApplicationServer(s *grpc.Server, srv SampleApplicationServer) { - s.RegisterService(&_SampleApplication_serviceDesc, srv) -} - -func _SampleApplication_DoUnaryUnary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(Message) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(SampleApplicationServer).DoUnaryUnary(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/SampleApplication/DoUnaryUnary", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(SampleApplicationServer).DoUnaryUnary(ctx, req.(*Message)) - } - return interceptor(ctx, in, info, handler) -} - -func _SampleApplication_DoUnaryStream_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(Message) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(SampleApplicationServer).DoUnaryStream(m, &sampleApplicationDoUnaryStreamServer{stream}) -} - -type SampleApplication_DoUnaryStreamServer interface { - Send(*Message) error - grpc.ServerStream -} - -type sampleApplicationDoUnaryStreamServer struct { - grpc.ServerStream -} - -func (x *sampleApplicationDoUnaryStreamServer) Send(m *Message) error { - return x.ServerStream.SendMsg(m) -} - -func _SampleApplication_DoStreamUnary_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(SampleApplicationServer).DoStreamUnary(&sampleApplicationDoStreamUnaryServer{stream}) -} - -type SampleApplication_DoStreamUnaryServer interface { - SendAndClose(*Message) error - Recv() (*Message, error) - grpc.ServerStream -} - -type sampleApplicationDoStreamUnaryServer struct { - grpc.ServerStream -} - -func (x *sampleApplicationDoStreamUnaryServer) SendAndClose(m *Message) error { - return x.ServerStream.SendMsg(m) -} - -func (x *sampleApplicationDoStreamUnaryServer) Recv() (*Message, error) { - m := new(Message) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func _SampleApplication_DoStreamStream_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(SampleApplicationServer).DoStreamStream(&sampleApplicationDoStreamStreamServer{stream}) -} - -type SampleApplication_DoStreamStreamServer interface { - Send(*Message) error - Recv() (*Message, error) - grpc.ServerStream -} - -type sampleApplicationDoStreamStreamServer struct { - grpc.ServerStream -} - -func (x *sampleApplicationDoStreamStreamServer) Send(m *Message) error { - return x.ServerStream.SendMsg(m) -} - -func (x *sampleApplicationDoStreamStreamServer) Recv() (*Message, error) { - m := new(Message) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -var _SampleApplication_serviceDesc = grpc.ServiceDesc{ - ServiceName: "SampleApplication", - HandlerType: (*SampleApplicationServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "DoUnaryUnary", - Handler: _SampleApplication_DoUnaryUnary_Handler, - }, - }, - Streams: []grpc.StreamDesc{ - { - StreamName: "DoUnaryStream", - Handler: _SampleApplication_DoUnaryStream_Handler, - ServerStreams: true, - }, - { - StreamName: "DoStreamUnary", - Handler: _SampleApplication_DoStreamUnary_Handler, - ClientStreams: true, - }, - { - StreamName: "DoStreamStream", - Handler: _SampleApplication_DoStreamStream_Handler, - ServerStreams: true, - ClientStreams: true, - }, - }, - Metadata: "sampleapp.proto", -} diff --git a/_integrations/nrgrpc/example/sampleapp/sampleapp.proto b/_integrations/nrgrpc/example/sampleapp/sampleapp.proto deleted file mode 100644 index 8ed50752b..000000000 --- a/_integrations/nrgrpc/example/sampleapp/sampleapp.proto +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -syntax = "proto3"; - -service SampleApplication { - rpc DoUnaryUnary(Message) returns (Message) {} - rpc DoUnaryStream(Message) returns (stream Message) {} - rpc DoStreamUnary(stream Message) returns (Message) {} - rpc DoStreamStream(stream Message) returns (stream Message) {} -} - -message Message { - string text = 1; -} diff --git a/_integrations/nrgrpc/example/server/server.go b/_integrations/nrgrpc/example/server/server.go deleted file mode 100644 index dc5c90e38..000000000 --- a/_integrations/nrgrpc/example/server/server.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - fmt "fmt" - "io" - "net" - "os" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrgrpc" - sampleapp "github.com/newrelic/go-agent/_integrations/nrgrpc/example/sampleapp" - "google.golang.org/grpc" -) - -// Server is a gRPC server. -type Server struct{} - -// processMessage processes each incoming Message. -func processMessage(ctx context.Context, msg *sampleapp.Message) { - defer newrelic.StartSegment(newrelic.FromContext(ctx), "processMessage").End() - fmt.Printf("Message received: %s\n", msg.Text) -} - -// DoUnaryUnary is a unary request, unary response method. -func (s *Server) DoUnaryUnary(ctx context.Context, msg *sampleapp.Message) (*sampleapp.Message, error) { - processMessage(ctx, msg) - return &sampleapp.Message{Text: "Hello from DoUnaryUnary"}, nil -} - -// DoUnaryStream is a unary request, stream response method. -func (s *Server) DoUnaryStream(msg *sampleapp.Message, stream sampleapp.SampleApplication_DoUnaryStreamServer) error { - processMessage(stream.Context(), msg) - for i := 0; i < 3; i++ { - if err := stream.Send(&sampleapp.Message{Text: "Hello from DoUnaryStream"}); nil != err { - return err - } - } - return nil -} - -// DoStreamUnary is a stream request, unary response method. -func (s *Server) DoStreamUnary(stream sampleapp.SampleApplication_DoStreamUnaryServer) error { - for { - msg, err := stream.Recv() - if err == io.EOF { - return stream.SendAndClose(&sampleapp.Message{Text: "Hello from DoStreamUnary"}) - } else if nil != err { - return err - } - processMessage(stream.Context(), msg) - } -} - -// DoStreamStream is a stream request, stream response method. -func (s *Server) DoStreamStream(stream sampleapp.SampleApplication_DoStreamStreamServer) error { - for { - msg, err := stream.Recv() - if err == io.EOF { - return nil - } else if nil != err { - return err - } - processMessage(stream.Context(), msg) - if err := stream.Send(&sampleapp.Message{Text: "Hello from DoStreamStream"}); nil != err { - return err - } - } -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("gRPC Server", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - - lis, err := net.Listen("tcp", "localhost:8080") - if err != nil { - panic(err) - } - grpcServer := grpc.NewServer( - // Add the New Relic gRPC server instrumentation - grpc.UnaryInterceptor(nrgrpc.UnaryServerInterceptor(app)), - grpc.StreamInterceptor(nrgrpc.StreamServerInterceptor(app)), - ) - sampleapp.RegisterSampleApplicationServer(grpcServer, &Server{}) - grpcServer.Serve(lis) -} diff --git a/_integrations/nrgrpc/nrgrpc_client.go b/_integrations/nrgrpc/nrgrpc_client.go deleted file mode 100644 index 20be96bc6..000000000 --- a/_integrations/nrgrpc/nrgrpc_client.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrgrpc - -import ( - "context" - "io" - "net/url" - "strings" - - newrelic "github.com/newrelic/go-agent" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" -) - -func getURL(method, target string) *url.URL { - var host string - // target can be anything from - // https://github.com/grpc/grpc/blob/master/doc/naming.md - // see https://godoc.org/google.golang.org/grpc#DialContext - if strings.HasPrefix(target, "unix:") { - host = "localhost" - } else { - host = strings.TrimPrefix(target, "dns:///") - } - return &url.URL{ - Scheme: "grpc", - Host: host, - Path: method, - } -} - -// startClientSegment starts an ExternalSegment and adds Distributed Trace -// headers to the outgoing grpc metadata in the context. -func startClientSegment(ctx context.Context, method, target string) (*newrelic.ExternalSegment, context.Context) { - var seg *newrelic.ExternalSegment - if txn := newrelic.FromContext(ctx); nil != txn { - seg = newrelic.StartExternalSegment(txn, nil) - - method = strings.TrimPrefix(method, "/") - seg.Host = getURL(method, target).Host - seg.Library = "gRPC" - seg.Procedure = method - - payload := txn.CreateDistributedTracePayload() - if txt := payload.Text(); "" != txt { - md, ok := metadata.FromOutgoingContext(ctx) - if !ok { - md = metadata.New(nil) - } - md.Set(newrelic.DistributedTracePayloadHeader, txt) - ctx = metadata.NewOutgoingContext(ctx, md) - } - } - - return seg, ctx -} - -// UnaryClientInterceptor instruments client unary RPCs. This interceptor -// records each unary call with an external segment. Using it requires two steps: -// -// 1. Use this function with grpc.WithChainUnaryInterceptor or -// grpc.WithUnaryInterceptor when creating a grpc.ClientConn. Example: -// -// conn, err := grpc.Dial( -// "localhost:8080", -// grpc.WithUnaryInterceptor(nrgrpc.UnaryClientInterceptor), -// grpc.WithStreamInterceptor(nrgrpc.StreamClientInterceptor), -// ) -// -// 2. Ensure that calls made with this grpc.ClientConn are done with a context -// which contains a newrelic.Transaction. -// -// Full example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrgrpc/example/client/client.go -// -// This interceptor only instruments unary calls. You must use both -// UnaryClientInterceptor and StreamClientInterceptor to instrument unary and -// streaming calls. These interceptors add headers to the call metadata if -// distributed tracing is enabled. -func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { - seg, ctx := startClientSegment(ctx, method, cc.Target()) - defer seg.End() - return invoker(ctx, method, req, reply, cc, opts...) -} - -type wrappedClientStream struct { - grpc.ClientStream - segment *newrelic.ExternalSegment - isUnaryServer bool -} - -func (s wrappedClientStream) RecvMsg(m interface{}) error { - err := s.ClientStream.RecvMsg(m) - if err == io.EOF || s.isUnaryServer { - s.segment.End() - } - return err -} - -// StreamClientInterceptor instruments client streaming RPCs. This interceptor -// records streaming each call with an external segment. Using it requires two steps: -// -// 1. Use this function with grpc.WithChainStreamInterceptor or -// grpc.WithStreamInterceptor when creating a grpc.ClientConn. Example: -// -// conn, err := grpc.Dial( -// "localhost:8080", -// grpc.WithUnaryInterceptor(nrgrpc.UnaryClientInterceptor), -// grpc.WithStreamInterceptor(nrgrpc.StreamClientInterceptor), -// ) -// -// 2. Ensure that calls made with this grpc.ClientConn are done with a context -// which contains a newrelic.Transaction. -// -// Full example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrgrpc/example/client/client.go -// -// This interceptor only instruments streaming calls. You must use both -// UnaryClientInterceptor and StreamClientInterceptor to instrument unary and -// streaming calls. These interceptors add headers to the call metadata if -// distributed tracing is enabled. -func StreamClientInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { - seg, ctx := startClientSegment(ctx, method, cc.Target()) - s, err := streamer(ctx, desc, cc, method, opts...) - if err != nil { - return s, err - } - return wrappedClientStream{ - segment: seg, - ClientStream: s, - isUnaryServer: !desc.ServerStreams, - }, nil -} diff --git a/_integrations/nrgrpc/nrgrpc_client_test.go b/_integrations/nrgrpc/nrgrpc_client_test.go deleted file mode 100644 index 2ab77ffba..000000000 --- a/_integrations/nrgrpc/nrgrpc_client_test.go +++ /dev/null @@ -1,597 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrgrpc - -import ( - "context" - "encoding/json" - "io" - "testing" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrgrpc/testapp" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" - "google.golang.org/grpc/metadata" -) - -func TestGetURL(t *testing.T) { - testcases := []struct { - method string - target string - expected string - }{ - { - method: "/TestApplication/DoUnaryUnary", - target: "", - expected: "grpc:///TestApplication/DoUnaryUnary", - }, - { - method: "TestApplication/DoUnaryUnary", - target: "", - expected: "grpc://TestApplication/DoUnaryUnary", - }, - { - method: "/TestApplication/DoUnaryUnary", - target: ":8080", - expected: "grpc://:8080/TestApplication/DoUnaryUnary", - }, - { - method: "/TestApplication/DoUnaryUnary", - target: "localhost:8080", - expected: "grpc://localhost:8080/TestApplication/DoUnaryUnary", - }, - { - method: "TestApplication/DoUnaryUnary", - target: "localhost:8080", - expected: "grpc://localhost:8080/TestApplication/DoUnaryUnary", - }, - { - method: "/TestApplication/DoUnaryUnary", - target: "dns:///localhost:8080", - expected: "grpc://localhost:8080/TestApplication/DoUnaryUnary", - }, - { - method: "/TestApplication/DoUnaryUnary", - target: "unix:/path/to/socket", - expected: "grpc://localhost/TestApplication/DoUnaryUnary", - }, - { - method: "/TestApplication/DoUnaryUnary", - target: "unix:///path/to/socket", - expected: "grpc://localhost/TestApplication/DoUnaryUnary", - }, - } - - for _, test := range testcases { - actual := getURL(test.method, test.target) - if actual.String() != test.expected { - t.Errorf("incorrect URL:\n\tmethod=%s,\n\ttarget=%s,\n\texpected=%s,\n\tactual=%s", - test.method, test.target, test.expected, actual.String()) - } - } -} - -func testApp() integrationsupport.ExpectApp { - return integrationsupport.NewTestApp(replyFn, configFn) -} - -var replyFn = func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.AccountID = "123" - reply.TrustedAccountKey = "123" - reply.PrimaryAppID = "456" -} - -var configFn = func(cfg *newrelic.Config) { - cfg.Enabled = false - cfg.DistributedTracer.Enabled = true - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 -} - -func TestUnaryClientInterceptor(t *testing.T) { - app := testApp() - txn := app.StartTransaction("UnaryUnary", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - resp, err := client.DoUnaryUnary(ctx, &testapp.Message{}) - if nil != err { - t.Fatal("client call to DoUnaryUnary failed", err) - } - var hdrs map[string][]string - err = json.Unmarshal([]byte(resp.Text), &hdrs) - if nil != err { - t.Fatal("cannot unmarshall client response", err) - } - if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] { - t.Error("distributed trace header not sent", hdrs) - } - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/UnaryUnary", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/UnaryUnary", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/bufnet/gRPC/TestApplication/DoUnaryUnary", Scope: "OtherTransaction/Go/UnaryUnary", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/UnaryUnary", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "http", - "component": "gRPC", - "name": "External/bufnet/gRPC/TestApplication/DoUnaryUnary", - "parentId": internal.MatchAnything, - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/UnaryUnary", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/UnaryUnary", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "External/bufnet/gRPC/TestApplication/DoUnaryUnary", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) -} - -func TestUnaryStreamClientInterceptor(t *testing.T) { - app := testApp() - txn := app.StartTransaction("UnaryStream", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - stream, err := client.DoUnaryStream(ctx, &testapp.Message{}) - if nil != err { - t.Fatal("client call to DoUnaryStream failed", err) - } - var recved int - for { - msg, err := stream.Recv() - if err == io.EOF { - break - } - if nil != err { - t.Fatal("error receiving message", err) - } - var hdrs map[string][]string - err = json.Unmarshal([]byte(msg.Text), &hdrs) - if nil != err { - t.Fatal("cannot unmarshall client response", err) - } - if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] { - t.Error("distributed trace header not sent", hdrs) - } - recved++ - } - if recved != 3 { - t.Fatal("received incorrect number of messages from server", recved) - } - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/UnaryStream", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/UnaryStream", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/bufnet/gRPC/TestApplication/DoUnaryStream", Scope: "OtherTransaction/Go/UnaryStream", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/UnaryStream", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "http", - "component": "gRPC", - "name": "External/bufnet/gRPC/TestApplication/DoUnaryStream", - "parentId": internal.MatchAnything, - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/UnaryStream", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/UnaryStream", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "External/bufnet/gRPC/TestApplication/DoUnaryStream", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) -} - -func TestStreamUnaryClientInterceptor(t *testing.T) { - app := testApp() - txn := app.StartTransaction("StreamUnary", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - stream, err := client.DoStreamUnary(ctx) - if nil != err { - t.Fatal("client call to DoStreamUnary failed", err) - } - for i := 0; i < 3; i++ { - if err := stream.Send(&testapp.Message{Text: "Hello DoStreamUnary"}); nil != err { - if err == io.EOF { - break - } - t.Fatal("failure to Send", err) - } - } - msg, err := stream.CloseAndRecv() - if nil != err { - t.Fatal("failure to CloseAndRecv", err) - } - var hdrs map[string][]string - err = json.Unmarshal([]byte(msg.Text), &hdrs) - if nil != err { - t.Fatal("cannot unmarshall client response", err) - } - if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] { - t.Error("distributed trace header not sent", hdrs) - } - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/StreamUnary", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/StreamUnary", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/bufnet/gRPC/TestApplication/DoStreamUnary", Scope: "OtherTransaction/Go/StreamUnary", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/StreamUnary", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "http", - "component": "gRPC", - "name": "External/bufnet/gRPC/TestApplication/DoStreamUnary", - "parentId": internal.MatchAnything, - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/StreamUnary", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/StreamUnary", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "External/bufnet/gRPC/TestApplication/DoStreamUnary", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) -} - -func TestStreamStreamClientInterceptor(t *testing.T) { - app := testApp() - txn := app.StartTransaction("StreamStream", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - stream, err := client.DoStreamStream(ctx) - if nil != err { - t.Fatal("client call to DoStreamStream failed", err) - } - waitc := make(chan struct{}) - go func() { - defer close(waitc) - var recved int - for { - msg, err := stream.Recv() - if err == io.EOF { - break - } - if err != nil { - t.Fatal("failure to Recv", err) - } - var hdrs map[string][]string - err = json.Unmarshal([]byte(msg.Text), &hdrs) - if nil != err { - t.Fatal("cannot unmarshall client response", err) - } - if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] { - t.Error("distributed trace header not sent", hdrs) - } - recved++ - } - if recved != 3 { - t.Fatal("received incorrect number of messages from server", recved) - } - }() - for i := 0; i < 3; i++ { - if err := stream.Send(&testapp.Message{Text: "Hello DoStreamStream"}); err != nil { - t.Fatal("failure to Send", err) - } - } - stream.CloseSend() - <-waitc - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/StreamStream", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/StreamStream", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/bufnet/gRPC/TestApplication/DoStreamStream", Scope: "OtherTransaction/Go/StreamStream", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/StreamStream", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "http", - "component": "gRPC", - "name": "External/bufnet/gRPC/TestApplication/DoStreamStream", - "parentId": internal.MatchAnything, - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/StreamStream", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/StreamStream", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "External/bufnet/gRPC/TestApplication/DoStreamStream", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) -} - -func TestClientUnaryMetadata(t *testing.T) { - // Test that metadata on the outgoing request are presevered - app := testApp() - txn := app.StartTransaction("metadata", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - - md := metadata.New(map[string]string{ - "testing": "hello world", - "newrelic": "payload", - }) - ctx = metadata.NewOutgoingContext(ctx, md) - - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - resp, err := client.DoUnaryUnary(ctx, &testapp.Message{}) - if nil != err { - t.Fatal("client call to DoUnaryUnary failed", err) - } - var hdrs map[string][]string - err = json.Unmarshal([]byte(resp.Text), &hdrs) - if nil != err { - t.Fatal("cannot unmarshall client response", err) - } - if hdr, ok := hdrs["newrelic"]; !ok || len(hdr) != 1 || "" == hdr[0] || "payload" == hdr[0] { - t.Error("distributed trace header not sent", hdrs) - } - if hdr, ok := hdrs["testing"]; !ok || len(hdr) != 1 || "hello world" != hdr[0] { - t.Error("testing header not sent", hdrs) - } -} - -func TestNilTxnClientUnary(t *testing.T) { - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - resp, err := client.DoUnaryUnary(context.Background(), &testapp.Message{}) - if nil != err { - t.Fatal("client call to DoUnaryUnary failed", err) - } - var hdrs map[string][]string - err = json.Unmarshal([]byte(resp.Text), &hdrs) - if nil != err { - t.Fatal("cannot unmarshall client response", err) - } - if _, ok := hdrs["newrelic"]; ok { - t.Error("distributed trace header sent", hdrs) - } -} - -func TestNilTxnClientStreaming(t *testing.T) { - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - stream, err := client.DoStreamUnary(context.Background()) - if nil != err { - t.Fatal("client call to DoStreamUnary failed", err) - } - for i := 0; i < 3; i++ { - if err := stream.Send(&testapp.Message{Text: "Hello DoStreamUnary"}); nil != err { - if err == io.EOF { - break - } - t.Fatal("failure to Send", err) - } - } - msg, err := stream.CloseAndRecv() - if nil != err { - t.Fatal("failure to CloseAndRecv", err) - } - var hdrs map[string][]string - err = json.Unmarshal([]byte(msg.Text), &hdrs) - if nil != err { - t.Fatal("cannot unmarshall client response", err) - } - if _, ok := hdrs["newrelic"]; ok { - t.Error("distributed trace header sent", hdrs) - } -} - -func TestClientStreamingError(t *testing.T) { - // Test that when creating the stream returns an error, no external - // segments are created - app := testApp() - txn := app.StartTransaction("UnaryStream", nil, nil) - - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - ctx = newrelic.NewContext(ctx, txn) - _, err := client.DoUnaryStream(ctx, &testapp.Message{}) - if nil == err { - t.Fatal("client call to DoUnaryStream did not return error") - } - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/UnaryStream", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/UnaryStream", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/UnaryStream", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/UnaryStream", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/UnaryStream", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{}, - }}, - }, - }}) -} diff --git a/_integrations/nrgrpc/nrgrpc_doc.go b/_integrations/nrgrpc/nrgrpc_doc.go deleted file mode 100644 index 293fa2cec..000000000 --- a/_integrations/nrgrpc/nrgrpc_doc.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrgrpc instruments https://github.com/grpc/grpc-go. -// -// This package can be used to instrument gRPC servers and gRPC clients. -// -// To instrument a gRPC server, use UnaryServerInterceptor and -// StreamServerInterceptor with your newrelic.Application to create server -// interceptors to pass to grpc.NewServer. Example: -// -// -// cfg := newrelic.NewConfig("gRPC Server", os.Getenv("NEW_RELIC_LICENSE_KEY")) -// app, _ := newrelic.NewApplication(cfg) -// server := grpc.NewServer( -// grpc.UnaryInterceptor(nrgrpc.UnaryServerInterceptor(app)), -// grpc.StreamInterceptor(nrgrpc.StreamServerInterceptor(app)), -// ) -// -// These interceptors create transactions for inbound calls. The transaction is -// added to the call context and can be accessed in your method handlers -// using newrelic.FromContext. -// -// Full server example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrgrpc/example/server/server.go -// -// To instrument a gRPC client, follow these two steps: -// -// 1. Use UnaryClientInterceptor and StreamClientInterceptor when creating a -// grpc.ClientConn. Example: -// -// conn, err := grpc.Dial( -// "localhost:8080", -// grpc.WithUnaryInterceptor(nrgrpc.UnaryClientInterceptor), -// grpc.WithStreamInterceptor(nrgrpc.StreamClientInterceptor), -// ) -// -// 2. Ensure that calls made with this grpc.ClientConn are done with a context -// which contains a newrelic.Transaction. -// -// Full client example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrgrpc/example/client/client.go -package nrgrpc - -import "github.com/newrelic/go-agent/internal" - -func init() { internal.TrackUsage("integration", "framework", "grpc") } diff --git a/_integrations/nrgrpc/nrgrpc_server.go b/_integrations/nrgrpc/nrgrpc_server.go deleted file mode 100644 index edc8d8515..000000000 --- a/_integrations/nrgrpc/nrgrpc_server.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrgrpc - -import ( - "context" - "net/http" - "strings" - - newrelic "github.com/newrelic/go-agent" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -func startTransaction(ctx context.Context, app newrelic.Application, fullMethod string) newrelic.Transaction { - method := strings.TrimPrefix(fullMethod, "/") - - var hdrs http.Header - if md, ok := metadata.FromIncomingContext(ctx); ok { - hdrs = make(http.Header, len(md)) - for k, vs := range md { - for _, v := range vs { - hdrs.Add(k, v) - } - } - } - - target := hdrs.Get(":authority") - url := getURL(method, target) - - webReq := newrelic.NewStaticWebRequest(hdrs, url, method, newrelic.TransportHTTP) - txn := app.StartTransaction(method, nil, nil) - txn.SetWebRequest(webReq) - - return txn -} - -// UnaryServerInterceptor instruments server unary RPCs. -// -// Use this function with grpc.UnaryInterceptor and a newrelic.Application to -// create a grpc.ServerOption to pass to grpc.NewServer. This interceptor -// records each unary call with a transaction. You must use both -// UnaryServerInterceptor and StreamServerInterceptor to instrument unary and -// streaming calls. -// -// Example: -// -// cfg := newrelic.NewConfig("gRPC Server", os.Getenv("NEW_RELIC_LICENSE_KEY")) -// app, _ := newrelic.NewApplication(cfg) -// server := grpc.NewServer( -// grpc.UnaryInterceptor(nrgrpc.UnaryServerInterceptor(app)), -// grpc.StreamInterceptor(nrgrpc.StreamServerInterceptor(app)), -// ) -// -// These interceptors add the transaction to the call context so it may be -// accessed in your method handlers using newrelic.FromContext. -// -// Full example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrgrpc/example/server/server.go -// -func UnaryServerInterceptor(app newrelic.Application) grpc.UnaryServerInterceptor { - if nil == app { - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - return handler(ctx, req) - } - } - - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { - txn := startTransaction(ctx, app, info.FullMethod) - defer txn.End() - - ctx = newrelic.NewContext(ctx, txn) - resp, err = handler(ctx, req) - txn.WriteHeader(int(status.Code(err))) - return - } -} - -type wrappedServerStream struct { - grpc.ServerStream - txn newrelic.Transaction -} - -func (s wrappedServerStream) Context() context.Context { - ctx := s.ServerStream.Context() - return newrelic.NewContext(ctx, s.txn) -} - -func newWrappedServerStream(stream grpc.ServerStream, txn newrelic.Transaction) grpc.ServerStream { - return wrappedServerStream{ - ServerStream: stream, - txn: txn, - } -} - -// StreamServerInterceptor instruments server streaming RPCs. -// -// Use this function with grpc.StreamInterceptor and a newrelic.Application to -// create a grpc.ServerOption to pass to grpc.NewServer. This interceptor -// records each streaming call with a transaction. You must use both -// UnaryServerInterceptor and StreamServerInterceptor to instrument unary and -// streaming calls. -// -// Example: -// -// cfg := newrelic.NewConfig("gRPC Server", os.Getenv("NEW_RELIC_LICENSE_KEY")) -// app, _ := newrelic.NewApplication(cfg) -// server := grpc.NewServer( -// grpc.UnaryInterceptor(nrgrpc.UnaryServerInterceptor(app)), -// grpc.StreamInterceptor(nrgrpc.StreamServerInterceptor(app)), -// ) -// -// These interceptors add the transaction to the call context so it may be -// accessed in your method handlers using newrelic.FromContext. -// -// Full example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrgrpc/example/server/server.go -// -func StreamServerInterceptor(app newrelic.Application) grpc.StreamServerInterceptor { - if nil == app { - return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - return handler(srv, ss) - } - } - - return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - txn := startTransaction(ss.Context(), app, info.FullMethod) - defer txn.End() - - err := handler(srv, newWrappedServerStream(ss, txn)) - txn.WriteHeader(int(status.Code(err))) - return err - } -} diff --git a/_integrations/nrgrpc/nrgrpc_server_test.go b/_integrations/nrgrpc/nrgrpc_server_test.go deleted file mode 100644 index 806e2dd3e..000000000 --- a/_integrations/nrgrpc/nrgrpc_server_test.go +++ /dev/null @@ -1,607 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrgrpc - -import ( - "context" - "io" - "net" - "strings" - "testing" - "time" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrgrpc/testapp" - "github.com/newrelic/go-agent/internal" - "google.golang.org/grpc" - "google.golang.org/grpc/test/bufconn" -) - -// newTestServerAndConn creates a new *grpc.Server and *grpc.ClientConn for use -// in testing. It adds instrumentation to both. If app is nil, then -// instrumentation is not applied to the server. Be sure to Stop() the server -// and Close() the connection when done with them. -func newTestServerAndConn(t *testing.T, app newrelic.Application) (*grpc.Server, *grpc.ClientConn) { - s := grpc.NewServer( - grpc.UnaryInterceptor(UnaryServerInterceptor(app)), - grpc.StreamInterceptor(StreamServerInterceptor(app)), - ) - testapp.RegisterTestApplicationServer(s, &testapp.Server{}) - lis := bufconn.Listen(1024 * 1024) - - go func() { - s.Serve(lis) - }() - - bufDialer := func(string, time.Duration) (net.Conn, error) { - return lis.Dial() - } - conn, err := grpc.Dial("bufnet", - grpc.WithDialer(bufDialer), - grpc.WithInsecure(), - grpc.WithBlock(), // create the connection synchronously - grpc.WithUnaryInterceptor(UnaryClientInterceptor), - grpc.WithStreamInterceptor(StreamClientInterceptor), - ) - if err != nil { - t.Fatal("failure to create ClientConn", err) - } - - return s, conn -} - -func TestUnaryServerInterceptor(t *testing.T) { - app := testApp() - - s, conn := newTestServerAndConn(t, app) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - txn := app.StartTransaction("client", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - _, err := client.DoUnaryUnary(ctx, &testapp.Message{}) - if nil != err { - t.Fatal("unable to call client DoUnaryUnary", err) - } - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/TestApplication/DoUnaryUnary", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/DoUnaryUnary", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/DoUnaryUnary", Scope: "WebTransaction/Go/TestApplication/DoUnaryUnary", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction/Go/TestApplication/DoUnaryUnary", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/TestApplication/DoUnaryUnary", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "guid": internal.MatchAnything, - "name": "WebTransaction/Go/TestApplication/DoUnaryUnary", - "nr.apdexPerfZone": internal.MatchAnything, - "parent.account": 123, - "parent.app": 456, - "parent.transportDuration": internal.MatchAnything, - "parent.transportType": "HTTP", - "parent.type": "App", - "parentId": internal.MatchAnything, - "parentSpanId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 0, - "request.headers.contentType": "application/grpc", - "request.method": "TestApplication/DoUnaryUnary", - "request.uri": "grpc://bufnet/TestApplication/DoUnaryUnary", - }, - }}) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "WebTransaction/Go/TestApplication/DoUnaryUnary", - "nr.entryPoint": true, - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "Custom/DoUnaryUnary", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestUnaryServerInterceptorError(t *testing.T) { - app := testApp() - - s, conn := newTestServerAndConn(t, app) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - _, err := client.DoUnaryUnaryError(context.Background(), &testapp.Message{}) - if nil == err { - t.Fatal("DoUnaryUnaryError should have returned an error") - } - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/TestApplication/DoUnaryUnaryError", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "Errors/WebTransaction/Go/TestApplication/DoUnaryUnaryError", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/all", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction/Go/TestApplication/DoUnaryUnaryError", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/TestApplication/DoUnaryUnaryError", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "guid": internal.MatchAnything, - "name": "WebTransaction/Go/TestApplication/DoUnaryUnaryError", - "nr.apdexPerfZone": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 15, - "request.headers.contentType": "application/grpc", - "request.method": "TestApplication/DoUnaryUnaryError", - "request.uri": "grpc://bufnet/TestApplication/DoUnaryUnaryError", - }, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "15", - "error.message": "response code 15", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - "transactionName": "WebTransaction/Go/TestApplication/DoUnaryUnaryError", - }, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 15, - "request.headers.User-Agent": internal.MatchAnything, - "request.headers.contentType": "application/grpc", - "request.method": "TestApplication/DoUnaryUnaryError", - "request.uri": "grpc://bufnet/TestApplication/DoUnaryUnaryError", - }, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestUnaryStreamServerInterceptor(t *testing.T) { - app := testApp() - - s, conn := newTestServerAndConn(t, app) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - txn := app.StartTransaction("client", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - stream, err := client.DoUnaryStream(ctx, &testapp.Message{}) - if nil != err { - t.Fatal("client call to DoUnaryStream failed", err) - } - var recved int - for { - _, err := stream.Recv() - if err == io.EOF { - break - } - if nil != err { - t.Fatal("error receiving message", err) - } - recved++ - } - if recved != 3 { - t.Fatal("received incorrect number of messages from server", recved) - } - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/TestApplication/DoUnaryStream", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/DoUnaryStream", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/DoUnaryStream", Scope: "WebTransaction/Go/TestApplication/DoUnaryStream", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction/Go/TestApplication/DoUnaryStream", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/TestApplication/DoUnaryStream", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "guid": internal.MatchAnything, - "name": "WebTransaction/Go/TestApplication/DoUnaryStream", - "nr.apdexPerfZone": internal.MatchAnything, - "parent.account": 123, - "parent.app": 456, - "parent.transportDuration": internal.MatchAnything, - "parent.transportType": "HTTP", - "parent.type": "App", - "parentId": internal.MatchAnything, - "parentSpanId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 0, - "request.headers.contentType": "application/grpc", - "request.method": "TestApplication/DoUnaryStream", - "request.uri": "grpc://bufnet/TestApplication/DoUnaryStream", - }, - }}) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "WebTransaction/Go/TestApplication/DoUnaryStream", - "nr.entryPoint": true, - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "Custom/DoUnaryStream", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestStreamUnaryServerInterceptor(t *testing.T) { - app := testApp() - - s, conn := newTestServerAndConn(t, app) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - txn := app.StartTransaction("client", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - stream, err := client.DoStreamUnary(ctx) - if nil != err { - t.Fatal("client call to DoStreamUnary failed", err) - } - for i := 0; i < 3; i++ { - if err := stream.Send(&testapp.Message{Text: "Hello DoStreamUnary"}); nil != err { - if err == io.EOF { - break - } - t.Fatal("failure to Send", err) - } - } - _, err = stream.CloseAndRecv() - if nil != err { - t.Fatal("failure to CloseAndRecv", err) - } - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/TestApplication/DoStreamUnary", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/DoStreamUnary", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/DoStreamUnary", Scope: "WebTransaction/Go/TestApplication/DoStreamUnary", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction/Go/TestApplication/DoStreamUnary", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/TestApplication/DoStreamUnary", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "guid": internal.MatchAnything, - "name": "WebTransaction/Go/TestApplication/DoStreamUnary", - "nr.apdexPerfZone": internal.MatchAnything, - "parent.account": 123, - "parent.app": 456, - "parent.transportDuration": internal.MatchAnything, - "parent.transportType": "HTTP", - "parent.type": "App", - "parentId": internal.MatchAnything, - "parentSpanId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 0, - "request.headers.contentType": "application/grpc", - "request.method": "TestApplication/DoStreamUnary", - "request.uri": "grpc://bufnet/TestApplication/DoStreamUnary", - }, - }}) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "WebTransaction/Go/TestApplication/DoStreamUnary", - "nr.entryPoint": true, - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "Custom/DoStreamUnary", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestStreamStreamServerInterceptor(t *testing.T) { - app := testApp() - - s, conn := newTestServerAndConn(t, app) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - txn := app.StartTransaction("client", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - stream, err := client.DoStreamStream(ctx) - if nil != err { - t.Fatal("client call to DoStreamStream failed", err) - } - waitc := make(chan struct{}) - go func() { - defer close(waitc) - var recved int - for { - _, err := stream.Recv() - if err == io.EOF { - break - } - if err != nil { - t.Fatal("failure to Recv", err) - } - recved++ - } - if recved != 3 { - t.Fatal("received incorrect number of messages from server", recved) - } - }() - for i := 0; i < 3; i++ { - if err := stream.Send(&testapp.Message{Text: "Hello DoStreamStream"}); err != nil { - t.Fatal("failure to Send", err) - } - } - stream.CloseSend() - <-waitc - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/TestApplication/DoStreamStream", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/DoStreamStream", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/DoStreamStream", Scope: "WebTransaction/Go/TestApplication/DoStreamStream", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction/Go/TestApplication/DoStreamStream", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/TestApplication/DoStreamStream", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "guid": internal.MatchAnything, - "name": "WebTransaction/Go/TestApplication/DoStreamStream", - "nr.apdexPerfZone": internal.MatchAnything, - "parent.account": 123, - "parent.app": 456, - "parent.transportDuration": internal.MatchAnything, - "parent.transportType": "HTTP", - "parent.type": "App", - "parentId": internal.MatchAnything, - "parentSpanId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 0, - "request.headers.contentType": "application/grpc", - "request.method": "TestApplication/DoStreamStream", - "request.uri": "grpc://bufnet/TestApplication/DoStreamStream", - }, - }}) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "WebTransaction/Go/TestApplication/DoStreamStream", - "nr.entryPoint": true, - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "Custom/DoStreamStream", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestStreamServerInterceptorError(t *testing.T) { - app := testApp() - - s, conn := newTestServerAndConn(t, app) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - stream, err := client.DoUnaryStreamError(context.Background(), &testapp.Message{}) - if nil != err { - t.Fatal("client call to DoUnaryStream failed", err) - } - _, err = stream.Recv() - if nil == err { - t.Fatal("DoUnaryStreamError should have returned an error") - } - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/TestApplication/DoUnaryStreamError", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "Errors/WebTransaction/Go/TestApplication/DoUnaryStreamError", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/all", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction/Go/TestApplication/DoUnaryStreamError", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/TestApplication/DoUnaryStreamError", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "guid": internal.MatchAnything, - "name": "WebTransaction/Go/TestApplication/DoUnaryStreamError", - "nr.apdexPerfZone": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 15, - "request.headers.contentType": "application/grpc", - "request.method": "TestApplication/DoUnaryStreamError", - "request.uri": "grpc://bufnet/TestApplication/DoUnaryStreamError", - }, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "15", - "error.message": "response code 15", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - "transactionName": "WebTransaction/Go/TestApplication/DoUnaryStreamError", - }, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 15, - "request.headers.User-Agent": internal.MatchAnything, - "request.headers.contentType": "application/grpc", - "request.method": "TestApplication/DoUnaryStreamError", - "request.uri": "grpc://bufnet/TestApplication/DoUnaryStreamError", - }, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestUnaryServerInterceptorNilApp(t *testing.T) { - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - msg, err := client.DoUnaryUnary(context.Background(), &testapp.Message{}) - if nil != err { - t.Fatal("unable to call client DoUnaryUnary", err) - } - if !strings.Contains(msg.Text, "content-type") { - t.Error("incorrect message received") - } -} - -func TestStreamServerInterceptorNilApp(t *testing.T) { - s, conn := newTestServerAndConn(t, nil) - defer s.Stop() - defer conn.Close() - - client := testapp.NewTestApplicationClient(conn) - stream, err := client.DoStreamUnary(context.Background()) - if nil != err { - t.Fatal("client call to DoStreamUnary failed", err) - } - for i := 0; i < 3; i++ { - if err := stream.Send(&testapp.Message{Text: "Hello DoStreamUnary"}); nil != err { - if err == io.EOF { - break - } - t.Fatal("failure to Send", err) - } - } - msg, err := stream.CloseAndRecv() - if nil != err { - t.Fatal("failure to CloseAndRecv", err) - } - if !strings.Contains(msg.Text, "content-type") { - t.Error("incorrect message received") - } -} - -func TestInterceptorsNilAppReturnNonNil(t *testing.T) { - uInt := UnaryServerInterceptor(nil) - if uInt == nil { - t.Error("UnaryServerInterceptor returned nil") - } - - sInt := StreamServerInterceptor(nil) - if sInt == nil { - t.Error("StreamServerInterceptor returned nil") - } -} diff --git a/_integrations/nrgrpc/testapp/README.md b/_integrations/nrgrpc/testapp/README.md deleted file mode 100644 index 81d7151e8..000000000 --- a/_integrations/nrgrpc/testapp/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Testing gRPC Application - -This directory contains a testing application for validating the New Relic gRPC -integration. The code in `testapp.pb.go` is generated using the following -command (to be run from the `_integrations/nrgrpc` directory). This command -should be rerun every time the `testapp.proto` file has changed for any reason. - -```bash -$ protoc -I testapp/ testapp/testapp.proto --go_out=plugins=grpc:testapp -``` - -To install required dependencies: - -```bash -go get -u google.golang.org/grpc -go get -u github.com/golang/protobuf/protoc-gen-go -``` diff --git a/_integrations/nrgrpc/testapp/server.go b/_integrations/nrgrpc/testapp/server.go deleted file mode 100644 index dcbccc1ed..000000000 --- a/_integrations/nrgrpc/testapp/server.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package testapp - -import ( - "context" - "encoding/json" - "io" - - newrelic "github.com/newrelic/go-agent" - codes "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - status "google.golang.org/grpc/status" -) - -// Server is a gRPC server. -type Server struct{} - -// DoUnaryUnary is a unary request, unary response method. -func (s *Server) DoUnaryUnary(ctx context.Context, msg *Message) (*Message, error) { - defer newrelic.StartSegment(newrelic.FromContext(ctx), "DoUnaryUnary").End() - md, _ := metadata.FromIncomingContext(ctx) - js, _ := json.Marshal(md) - return &Message{Text: string(js)}, nil -} - -// DoUnaryStream is a unary request, stream response method. -func (s *Server) DoUnaryStream(msg *Message, stream TestApplication_DoUnaryStreamServer) error { - defer newrelic.StartSegment(newrelic.FromContext(stream.Context()), "DoUnaryStream").End() - md, _ := metadata.FromIncomingContext(stream.Context()) - js, _ := json.Marshal(md) - for i := 0; i < 3; i++ { - if err := stream.Send(&Message{Text: string(js)}); nil != err { - return err - } - } - return nil -} - -// DoStreamUnary is a stream request, unary response method. -func (s *Server) DoStreamUnary(stream TestApplication_DoStreamUnaryServer) error { - defer newrelic.StartSegment(newrelic.FromContext(stream.Context()), "DoStreamUnary").End() - md, _ := metadata.FromIncomingContext(stream.Context()) - js, _ := json.Marshal(md) - for { - _, err := stream.Recv() - if err == io.EOF { - return stream.SendAndClose(&Message{Text: string(js)}) - } else if nil != err { - return err - } - } -} - -// DoStreamStream is a stream request, stream response method. -func (s *Server) DoStreamStream(stream TestApplication_DoStreamStreamServer) error { - defer newrelic.StartSegment(newrelic.FromContext(stream.Context()), "DoStreamStream").End() - md, _ := metadata.FromIncomingContext(stream.Context()) - js, _ := json.Marshal(md) - for { - _, err := stream.Recv() - if err == io.EOF { - return nil - } else if nil != err { - return err - } - if err := stream.Send(&Message{Text: string(js)}); nil != err { - return err - } - } -} - -// DoUnaryUnaryError is a unary request, unary response method that returns an -// error. -func (s *Server) DoUnaryUnaryError(ctx context.Context, msg *Message) (*Message, error) { - return &Message{}, status.New(codes.DataLoss, "oooooops!").Err() -} - -// DoUnaryStreamError is a unary request, unary response method that returns an -// error. -func (s *Server) DoUnaryStreamError(msg *Message, stream TestApplication_DoUnaryStreamErrorServer) error { - return status.New(codes.DataLoss, "oooooops!").Err() -} diff --git a/_integrations/nrgrpc/testapp/testapp.pb.go b/_integrations/nrgrpc/testapp/testapp.pb.go deleted file mode 100644 index d95c75b24..000000000 --- a/_integrations/nrgrpc/testapp/testapp.pb.go +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: testapp.proto - -package testapp - -import ( - context "context" - fmt "fmt" - proto "github.com/golang/protobuf/proto" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package - -type Message struct { - Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Message) Reset() { *m = Message{} } -func (m *Message) String() string { return proto.CompactTextString(m) } -func (*Message) ProtoMessage() {} -func (*Message) Descriptor() ([]byte, []int) { - return fileDescriptor_98d4e818d9f182b1, []int{0} -} - -func (m *Message) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Message.Unmarshal(m, b) -} -func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Message.Marshal(b, m, deterministic) -} -func (m *Message) XXX_Merge(src proto.Message) { - xxx_messageInfo_Message.Merge(m, src) -} -func (m *Message) XXX_Size() int { - return xxx_messageInfo_Message.Size(m) -} -func (m *Message) XXX_DiscardUnknown() { - xxx_messageInfo_Message.DiscardUnknown(m) -} - -var xxx_messageInfo_Message proto.InternalMessageInfo - -func (m *Message) GetText() string { - if m != nil { - return m.Text - } - return "" -} - -func init() { - proto.RegisterType((*Message)(nil), "Message") -} - -func init() { proto.RegisterFile("testapp.proto", fileDescriptor_98d4e818d9f182b1) } - -var fileDescriptor_98d4e818d9f182b1 = []byte{ - // 175 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2d, 0x49, 0x2d, 0x2e, - 0x49, 0x2c, 0x28, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x92, 0xe5, 0x62, 0xf7, 0x4d, 0x2d, - 0x2e, 0x4e, 0x4c, 0x4f, 0x15, 0x12, 0xe2, 0x62, 0x29, 0x49, 0xad, 0x28, 0x91, 0x60, 0x54, 0x60, - 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x8d, 0xfa, 0x98, 0xb8, 0xf8, 0x43, 0x52, 0x8b, 0x4b, 0x1c, 0x0b, - 0x0a, 0x72, 0x32, 0x93, 0x13, 0x4b, 0x32, 0xf3, 0xf3, 0x84, 0x54, 0xb8, 0x78, 0x5c, 0xf2, 0x43, - 0xf3, 0x12, 0x8b, 0x2a, 0xc1, 0x84, 0x10, 0x87, 0x1e, 0xd4, 0x04, 0x29, 0x38, 0x4b, 0x89, 0x41, - 0x48, 0x9d, 0x8b, 0x17, 0xaa, 0x2a, 0xb8, 0xa4, 0x28, 0x35, 0x31, 0x17, 0xbb, 0x32, 0x03, 0x46, - 0x88, 0x42, 0x88, 0x1a, 0x3c, 0xe6, 0x69, 0x30, 0x0a, 0x69, 0x71, 0xf1, 0xc1, 0x14, 0xe2, 0x33, - 0x52, 0x83, 0xd1, 0x80, 0x51, 0x48, 0x93, 0x4b, 0x10, 0xd9, 0x8d, 0xae, 0x45, 0x45, 0xf9, 0x45, - 0x38, 0x1c, 0xaa, 0xc3, 0x25, 0x84, 0xe2, 0x50, 0x3c, 0x6a, 0x0d, 0x18, 0x93, 0xd8, 0xc0, 0xc1, - 0x66, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xb8, 0xca, 0x5d, 0x32, 0x47, 0x01, 0x00, 0x00, -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// TestApplicationClient is the client API for TestApplication service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type TestApplicationClient interface { - DoUnaryUnary(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) - DoUnaryStream(ctx context.Context, in *Message, opts ...grpc.CallOption) (TestApplication_DoUnaryStreamClient, error) - DoStreamUnary(ctx context.Context, opts ...grpc.CallOption) (TestApplication_DoStreamUnaryClient, error) - DoStreamStream(ctx context.Context, opts ...grpc.CallOption) (TestApplication_DoStreamStreamClient, error) - DoUnaryUnaryError(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) - DoUnaryStreamError(ctx context.Context, in *Message, opts ...grpc.CallOption) (TestApplication_DoUnaryStreamErrorClient, error) -} - -type testApplicationClient struct { - cc *grpc.ClientConn -} - -func NewTestApplicationClient(cc *grpc.ClientConn) TestApplicationClient { - return &testApplicationClient{cc} -} - -func (c *testApplicationClient) DoUnaryUnary(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) { - out := new(Message) - err := c.cc.Invoke(ctx, "/TestApplication/DoUnaryUnary", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *testApplicationClient) DoUnaryStream(ctx context.Context, in *Message, opts ...grpc.CallOption) (TestApplication_DoUnaryStreamClient, error) { - stream, err := c.cc.NewStream(ctx, &_TestApplication_serviceDesc.Streams[0], "/TestApplication/DoUnaryStream", opts...) - if err != nil { - return nil, err - } - x := &testApplicationDoUnaryStreamClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type TestApplication_DoUnaryStreamClient interface { - Recv() (*Message, error) - grpc.ClientStream -} - -type testApplicationDoUnaryStreamClient struct { - grpc.ClientStream -} - -func (x *testApplicationDoUnaryStreamClient) Recv() (*Message, error) { - m := new(Message) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *testApplicationClient) DoStreamUnary(ctx context.Context, opts ...grpc.CallOption) (TestApplication_DoStreamUnaryClient, error) { - stream, err := c.cc.NewStream(ctx, &_TestApplication_serviceDesc.Streams[1], "/TestApplication/DoStreamUnary", opts...) - if err != nil { - return nil, err - } - x := &testApplicationDoStreamUnaryClient{stream} - return x, nil -} - -type TestApplication_DoStreamUnaryClient interface { - Send(*Message) error - CloseAndRecv() (*Message, error) - grpc.ClientStream -} - -type testApplicationDoStreamUnaryClient struct { - grpc.ClientStream -} - -func (x *testApplicationDoStreamUnaryClient) Send(m *Message) error { - return x.ClientStream.SendMsg(m) -} - -func (x *testApplicationDoStreamUnaryClient) CloseAndRecv() (*Message, error) { - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - m := new(Message) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *testApplicationClient) DoStreamStream(ctx context.Context, opts ...grpc.CallOption) (TestApplication_DoStreamStreamClient, error) { - stream, err := c.cc.NewStream(ctx, &_TestApplication_serviceDesc.Streams[2], "/TestApplication/DoStreamStream", opts...) - if err != nil { - return nil, err - } - x := &testApplicationDoStreamStreamClient{stream} - return x, nil -} - -type TestApplication_DoStreamStreamClient interface { - Send(*Message) error - Recv() (*Message, error) - grpc.ClientStream -} - -type testApplicationDoStreamStreamClient struct { - grpc.ClientStream -} - -func (x *testApplicationDoStreamStreamClient) Send(m *Message) error { - return x.ClientStream.SendMsg(m) -} - -func (x *testApplicationDoStreamStreamClient) Recv() (*Message, error) { - m := new(Message) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func (c *testApplicationClient) DoUnaryUnaryError(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) { - out := new(Message) - err := c.cc.Invoke(ctx, "/TestApplication/DoUnaryUnaryError", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *testApplicationClient) DoUnaryStreamError(ctx context.Context, in *Message, opts ...grpc.CallOption) (TestApplication_DoUnaryStreamErrorClient, error) { - stream, err := c.cc.NewStream(ctx, &_TestApplication_serviceDesc.Streams[3], "/TestApplication/DoUnaryStreamError", opts...) - if err != nil { - return nil, err - } - x := &testApplicationDoUnaryStreamErrorClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type TestApplication_DoUnaryStreamErrorClient interface { - Recv() (*Message, error) - grpc.ClientStream -} - -type testApplicationDoUnaryStreamErrorClient struct { - grpc.ClientStream -} - -func (x *testApplicationDoUnaryStreamErrorClient) Recv() (*Message, error) { - m := new(Message) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -// TestApplicationServer is the server API for TestApplication service. -type TestApplicationServer interface { - DoUnaryUnary(context.Context, *Message) (*Message, error) - DoUnaryStream(*Message, TestApplication_DoUnaryStreamServer) error - DoStreamUnary(TestApplication_DoStreamUnaryServer) error - DoStreamStream(TestApplication_DoStreamStreamServer) error - DoUnaryUnaryError(context.Context, *Message) (*Message, error) - DoUnaryStreamError(*Message, TestApplication_DoUnaryStreamErrorServer) error -} - -// UnimplementedTestApplicationServer can be embedded to have forward compatible implementations. -type UnimplementedTestApplicationServer struct { -} - -func (*UnimplementedTestApplicationServer) DoUnaryUnary(ctx context.Context, req *Message) (*Message, error) { - return nil, status.Errorf(codes.Unimplemented, "method DoUnaryUnary not implemented") -} -func (*UnimplementedTestApplicationServer) DoUnaryStream(req *Message, srv TestApplication_DoUnaryStreamServer) error { - return status.Errorf(codes.Unimplemented, "method DoUnaryStream not implemented") -} -func (*UnimplementedTestApplicationServer) DoStreamUnary(srv TestApplication_DoStreamUnaryServer) error { - return status.Errorf(codes.Unimplemented, "method DoStreamUnary not implemented") -} -func (*UnimplementedTestApplicationServer) DoStreamStream(srv TestApplication_DoStreamStreamServer) error { - return status.Errorf(codes.Unimplemented, "method DoStreamStream not implemented") -} -func (*UnimplementedTestApplicationServer) DoUnaryUnaryError(ctx context.Context, req *Message) (*Message, error) { - return nil, status.Errorf(codes.Unimplemented, "method DoUnaryUnaryError not implemented") -} -func (*UnimplementedTestApplicationServer) DoUnaryStreamError(req *Message, srv TestApplication_DoUnaryStreamErrorServer) error { - return status.Errorf(codes.Unimplemented, "method DoUnaryStreamError not implemented") -} - -func RegisterTestApplicationServer(s *grpc.Server, srv TestApplicationServer) { - s.RegisterService(&_TestApplication_serviceDesc, srv) -} - -func _TestApplication_DoUnaryUnary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(Message) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(TestApplicationServer).DoUnaryUnary(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/TestApplication/DoUnaryUnary", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TestApplicationServer).DoUnaryUnary(ctx, req.(*Message)) - } - return interceptor(ctx, in, info, handler) -} - -func _TestApplication_DoUnaryStream_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(Message) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(TestApplicationServer).DoUnaryStream(m, &testApplicationDoUnaryStreamServer{stream}) -} - -type TestApplication_DoUnaryStreamServer interface { - Send(*Message) error - grpc.ServerStream -} - -type testApplicationDoUnaryStreamServer struct { - grpc.ServerStream -} - -func (x *testApplicationDoUnaryStreamServer) Send(m *Message) error { - return x.ServerStream.SendMsg(m) -} - -func _TestApplication_DoStreamUnary_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(TestApplicationServer).DoStreamUnary(&testApplicationDoStreamUnaryServer{stream}) -} - -type TestApplication_DoStreamUnaryServer interface { - SendAndClose(*Message) error - Recv() (*Message, error) - grpc.ServerStream -} - -type testApplicationDoStreamUnaryServer struct { - grpc.ServerStream -} - -func (x *testApplicationDoStreamUnaryServer) SendAndClose(m *Message) error { - return x.ServerStream.SendMsg(m) -} - -func (x *testApplicationDoStreamUnaryServer) Recv() (*Message, error) { - m := new(Message) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func _TestApplication_DoStreamStream_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(TestApplicationServer).DoStreamStream(&testApplicationDoStreamStreamServer{stream}) -} - -type TestApplication_DoStreamStreamServer interface { - Send(*Message) error - Recv() (*Message, error) - grpc.ServerStream -} - -type testApplicationDoStreamStreamServer struct { - grpc.ServerStream -} - -func (x *testApplicationDoStreamStreamServer) Send(m *Message) error { - return x.ServerStream.SendMsg(m) -} - -func (x *testApplicationDoStreamStreamServer) Recv() (*Message, error) { - m := new(Message) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -func _TestApplication_DoUnaryUnaryError_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(Message) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(TestApplicationServer).DoUnaryUnaryError(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/TestApplication/DoUnaryUnaryError", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TestApplicationServer).DoUnaryUnaryError(ctx, req.(*Message)) - } - return interceptor(ctx, in, info, handler) -} - -func _TestApplication_DoUnaryStreamError_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(Message) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(TestApplicationServer).DoUnaryStreamError(m, &testApplicationDoUnaryStreamErrorServer{stream}) -} - -type TestApplication_DoUnaryStreamErrorServer interface { - Send(*Message) error - grpc.ServerStream -} - -type testApplicationDoUnaryStreamErrorServer struct { - grpc.ServerStream -} - -func (x *testApplicationDoUnaryStreamErrorServer) Send(m *Message) error { - return x.ServerStream.SendMsg(m) -} - -var _TestApplication_serviceDesc = grpc.ServiceDesc{ - ServiceName: "TestApplication", - HandlerType: (*TestApplicationServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "DoUnaryUnary", - Handler: _TestApplication_DoUnaryUnary_Handler, - }, - { - MethodName: "DoUnaryUnaryError", - Handler: _TestApplication_DoUnaryUnaryError_Handler, - }, - }, - Streams: []grpc.StreamDesc{ - { - StreamName: "DoUnaryStream", - Handler: _TestApplication_DoUnaryStream_Handler, - ServerStreams: true, - }, - { - StreamName: "DoStreamUnary", - Handler: _TestApplication_DoStreamUnary_Handler, - ClientStreams: true, - }, - { - StreamName: "DoStreamStream", - Handler: _TestApplication_DoStreamStream_Handler, - ServerStreams: true, - ClientStreams: true, - }, - { - StreamName: "DoUnaryStreamError", - Handler: _TestApplication_DoUnaryStreamError_Handler, - ServerStreams: true, - }, - }, - Metadata: "testapp.proto", -} diff --git a/_integrations/nrgrpc/testapp/testapp.proto b/_integrations/nrgrpc/testapp/testapp.proto deleted file mode 100644 index 99f9aecb5..000000000 --- a/_integrations/nrgrpc/testapp/testapp.proto +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -syntax = "proto3"; - -service TestApplication { - rpc DoUnaryUnary(Message) returns (Message) {} - rpc DoUnaryStream(Message) returns (stream Message) {} - rpc DoStreamUnary(stream Message) returns (Message) {} - rpc DoStreamStream(stream Message) returns (stream Message) {} - - rpc DoUnaryUnaryError(Message) returns (Message) {} - rpc DoUnaryStreamError(Message) returns (stream Message) {} -} - -message Message { - string text = 1; -} diff --git a/_integrations/nrhttprouter/README.md b/_integrations/nrhttprouter/README.md deleted file mode 100644 index 8570c3b7b..000000000 --- a/_integrations/nrhttprouter/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrhttprouter [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrhttprouter?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrhttprouter) - -Package `nrhttprouter` instruments https://github.com/julienschmidt/httprouter applications. - -```go -import "github.com/newrelic/go-agent/_integrations/nrhttprouter" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrhttprouter). diff --git a/_integrations/nrhttprouter/example/main.go b/_integrations/nrhttprouter/example/main.go deleted file mode 100644 index b359f7e17..000000000 --- a/_integrations/nrhttprouter/example/main.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "net/http" - "os" - - "github.com/julienschmidt/httprouter" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrhttprouter" -) - -func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - w.Write([]byte("welcome\n")) -} - -func hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - w.Write([]byte(fmt.Sprintf("hello %s\n", ps.ByName("name")))) -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("httprouter App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - // Use an *nrhttprouter.Router in place of an *httprouter.Router. - router := nrhttprouter.New(app) - - router.GET("/", index) - router.GET("/hello/:name", hello) - - http.ListenAndServe(":8000", router) -} diff --git a/_integrations/nrhttprouter/nrhttprouter.go b/_integrations/nrhttprouter/nrhttprouter.go deleted file mode 100644 index 5c29b46f3..000000000 --- a/_integrations/nrhttprouter/nrhttprouter.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrhttprouter instruments https://github.com/julienschmidt/httprouter -// applications. -// -// Use this package to instrument inbound requests handled by a -// httprouter.Router. Use an *nrhttprouter.Router in place of your -// *httprouter.Router. Example: -// -// package main -// -// import ( -// "fmt" -// "net/http" -// "os" -// -// "github.com/julienschmidt/httprouter" -// newrelic "github.com/newrelic/go-agent" -// "github.com/newrelic/go-agent/_integrations/nrhttprouter" -// ) -// -// func main() { -// cfg := newrelic.NewConfig("httprouter App", os.Getenv("NEW_RELIC_LICENSE_KEY")) -// app, _ := newrelic.NewApplication(cfg) -// -// // Create the Router replacement: -// router := nrhttprouter.New(app) -// -// router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { -// w.Write([]byte("welcome\n")) -// }) -// router.GET("/hello/:name", (w http.ResponseWriter, r *http.Request, ps httprouter.Params) { -// w.Write([]byte(fmt.Sprintf("hello %s\n", ps.ByName("name")))) -// }) -// http.ListenAndServe(":8000", router) -// } -// -// Runnable example: https://github.com/newrelic/go-agent/tree/master/_integrations/nrhttprouter/example/main.go -package nrhttprouter - -import ( - "net/http" - - "github.com/julienschmidt/httprouter" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" -) - -func init() { internal.TrackUsage("integration", "framework", "httprouter") } - -// Router should be used in place of httprouter.Router. Create it using -// New(). -type Router struct { - *httprouter.Router - - application newrelic.Application -} - -// New creates a new Router to be used in place of httprouter.Router. -func New(app newrelic.Application) *Router { - return &Router{ - Router: httprouter.New(), - application: app, - } -} - -func txnName(method, path string) string { - return method + " " + path -} - -func (r *Router) handle(method string, path string, original httprouter.Handle) { - handle := original - if nil != r.application { - handle = func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { - txn := r.application.StartTransaction(txnName(method, path), w, req) - defer txn.End() - - req = newrelic.RequestWithTransactionContext(req, txn) - - original(txn, req, ps) - } - } - r.Router.Handle(method, path, handle) -} - -// DELETE replaces httprouter.Router.DELETE. -func (r *Router) DELETE(path string, h httprouter.Handle) { - r.handle(http.MethodDelete, path, h) -} - -// GET replaces httprouter.Router.GET. -func (r *Router) GET(path string, h httprouter.Handle) { - r.handle(http.MethodGet, path, h) -} - -// HEAD replaces httprouter.Router.HEAD. -func (r *Router) HEAD(path string, h httprouter.Handle) { - r.handle(http.MethodHead, path, h) -} - -// OPTIONS replaces httprouter.Router.OPTIONS. -func (r *Router) OPTIONS(path string, h httprouter.Handle) { - r.handle(http.MethodOptions, path, h) -} - -// PATCH replaces httprouter.Router.PATCH. -func (r *Router) PATCH(path string, h httprouter.Handle) { - r.handle(http.MethodPatch, path, h) -} - -// POST replaces httprouter.Router.POST. -func (r *Router) POST(path string, h httprouter.Handle) { - r.handle(http.MethodPost, path, h) -} - -// PUT replaces httprouter.Router.PUT. -func (r *Router) PUT(path string, h httprouter.Handle) { - r.handle(http.MethodPut, path, h) -} - -// Handle replaces httprouter.Router.Handle. -func (r *Router) Handle(method, path string, h httprouter.Handle) { - r.handle(method, path, h) -} - -// Handler replaces httprouter.Router.Handler. -func (r *Router) Handler(method, path string, handler http.Handler) { - _, h := newrelic.WrapHandle(r.application, txnName(method, path), handler) - r.Router.Handler(method, path, h) -} - -// HandlerFunc replaces httprouter.Router.HandlerFunc. -func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) { - r.Handler(method, path, handler) -} - -// ServeHTTP replaces httprouter.Router.ServeHTTP. -func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if nil != r.application { - h, _, _ := r.Router.Lookup(req.Method, req.URL.Path) - if nil == h { - txn := r.application.StartTransaction("NotFound", w, req) - defer txn.End() - w = txn - req = newrelic.RequestWithTransactionContext(req, txn) - } - } - - r.Router.ServeHTTP(w, req) -} diff --git a/_integrations/nrhttprouter/nrhttprouter_test.go b/_integrations/nrhttprouter/nrhttprouter_test.go deleted file mode 100644 index 2b853fe1f..000000000 --- a/_integrations/nrhttprouter/nrhttprouter_test.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrhttprouter - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/julienschmidt/httprouter" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -func TestMethodFunctions(t *testing.T) { - - methodFuncs := []struct { - Method string - Fn func(*Router) func(string, httprouter.Handle) - }{ - {Method: "DELETE", Fn: func(r *Router) func(string, httprouter.Handle) { return r.DELETE }}, - {Method: "GET", Fn: func(r *Router) func(string, httprouter.Handle) { return r.GET }}, - {Method: "HEAD", Fn: func(r *Router) func(string, httprouter.Handle) { return r.HEAD }}, - {Method: "OPTIONS", Fn: func(r *Router) func(string, httprouter.Handle) { return r.OPTIONS }}, - {Method: "PATCH", Fn: func(r *Router) func(string, httprouter.Handle) { return r.PATCH }}, - {Method: "POST", Fn: func(r *Router) func(string, httprouter.Handle) { return r.POST }}, - {Method: "PUT", Fn: func(r *Router) func(string, httprouter.Handle) { return r.PUT }}, - } - - for _, md := range methodFuncs { - app := integrationsupport.NewBasicTestApp() - router := New(app) - md.Fn(router)("/hello/:name", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - // Test that the Transaction is used as the response writer. - w.WriteHeader(500) - w.Write([]byte(fmt.Sprintf("hi %s", ps.ByName("name")))) - }) - response := httptest.NewRecorder() - req, err := http.NewRequest(md.Method, "/hello/person", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "hi person" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: md.Method + " /hello/:name", - IsWeb: true, - NumErrors: 1, - }) - } -} - -func TestGetNoApplication(t *testing.T) { - router := New(nil) - - router.GET("/hello/:name", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - w.Write([]byte(fmt.Sprintf("hi %s", ps.ByName("name")))) - }) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello/person", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "hi person" { - t.Error("wrong response body", respBody) - } -} - -func TestHandle(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := New(app) - - router.Handle("GET", "/hello/:name", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - // Test that the Transaction is used as the response writer. - w.WriteHeader(500) - w.Write([]byte(fmt.Sprintf("hi %s", ps.ByName("name")))) - if txn := newrelic.FromContext(r.Context()); txn != nil { - txn.AddAttribute("color", "purple") - } - }) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello/person", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "hi person" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "GET /hello/:name", - IsWeb: true, - NumErrors: 1, - }) - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/GET /hello/:name", - "nr.apdexPerfZone": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{ - "color": "purple", - }, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 500, - "request.method": "GET", - "request.uri": "/hello/person", - }, - }, - }) -} - -func TestHandler(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := New(app) - - router.Handler("GET", "/hello/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Test that the Transaction is used as the response writer. - w.WriteHeader(500) - w.Write([]byte("hi there")) - if txn := newrelic.FromContext(r.Context()); txn != nil { - txn.AddAttribute("color", "purple") - } - })) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello/", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "hi there" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "GET /hello/", - IsWeb: true, - NumErrors: 1, - }) - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/GET /hello/", - "nr.apdexPerfZone": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{ - "color": "purple", - }, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 500, - "request.method": "GET", - "request.uri": "/hello/", - }, - }, - }) -} - -func TestHandlerMissingApplication(t *testing.T) { - router := New(nil) - - router.Handler("GET", "/hello/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(500) - w.Write([]byte("hi there")) - })) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello/", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "hi there" { - t.Error("wrong response body", respBody) - } -} - -func TestHandlerFunc(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := New(app) - - router.HandlerFunc("GET", "/hello/", func(w http.ResponseWriter, r *http.Request) { - // Test that the Transaction is used as the response writer. - w.WriteHeader(500) - w.Write([]byte("hi there")) - }) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello/", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "hi there" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "GET /hello/", - IsWeb: true, - NumErrors: 1, - }) -} - -func TestNotFound(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := New(app) - - router.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Test that the Transaction is used as the response writer. - w.WriteHeader(500) - w.Write([]byte("not found!")) - if txn := newrelic.FromContext(r.Context()); txn != nil { - txn.AddAttribute("color", "purple") - } - }) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello/", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "not found!" { - t.Error("wrong response body", respBody) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "NotFound", - IsWeb: true, - NumErrors: 1, - }) - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/NotFound", - "nr.apdexPerfZone": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{ - "color": "purple", - }, - AgentAttributes: map[string]interface{}{ - "httpResponseCode": 500, - "request.method": "GET", - "request.uri": "/hello/", - }, - }, - }) -} - -func TestNotFoundMissingApplication(t *testing.T) { - router := New(nil) - - router.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Test that the Transaction is used as the response writer. - w.WriteHeader(500) - w.Write([]byte("not found!")) - }) - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello/", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if respBody := response.Body.String(); respBody != "not found!" { - t.Error("wrong response body", respBody) - } -} - -func TestNotFoundNotSet(t *testing.T) { - app := integrationsupport.NewBasicTestApp() - router := New(app) - - response := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/hello/", nil) - if err != nil { - t.Fatal(err) - } - router.ServeHTTP(response, req) - if response.Code != 404 { - t.Error(response.Code) - } - app.ExpectTxnMetrics(t, internal.WantTxn{ - Name: "NotFound", - IsWeb: true, - }) -} diff --git a/_integrations/nrlambda/README.md b/_integrations/nrlambda/README.md deleted file mode 100644 index b70a5d05a..000000000 --- a/_integrations/nrlambda/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrlambda [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlambda?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlambda) - -Package `nrlambda` adds support for AWS Lambda. - -```go -import "github.com/newrelic/go-agent/_integrations/nrlambda" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlambda). diff --git a/_integrations/nrlambda/config.go b/_integrations/nrlambda/config.go deleted file mode 100644 index 1fd44c4b6..000000000 --- a/_integrations/nrlambda/config.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrlambda - -import ( - "os" - "time" - - newrelic "github.com/newrelic/go-agent" -) - -// NewConfig populates a newrelic.Config with correct default settings for a -// Lambda serverless environment. NewConfig will populate fields based on -// environment variables common to all New Relic agents that support Lambda. -// Environment variables NEW_RELIC_ACCOUNT_ID, NEW_RELIC_TRUSTED_ACCOUNT_KEY, -// and NEW_RELIC_PRIMARY_APPLICATION_ID configure fields required for -// distributed tracing. Environment variable NEW_RELIC_APDEX_T may be used to -// set a custom apdex threshold. -func NewConfig() newrelic.Config { - return newConfigInternal(os.Getenv) -} - -func newConfigInternal(getenv func(string) string) newrelic.Config { - cfg := newrelic.NewConfig("", "") - - cfg.ServerlessMode.Enabled = true - - cfg.ServerlessMode.AccountID = getenv("NEW_RELIC_ACCOUNT_ID") - cfg.ServerlessMode.TrustedAccountKey = getenv("NEW_RELIC_TRUSTED_ACCOUNT_KEY") - cfg.ServerlessMode.PrimaryAppID = getenv("NEW_RELIC_PRIMARY_APPLICATION_ID") - - cfg.DistributedTracer.Enabled = true - - if s := getenv("NEW_RELIC_APDEX_T"); "" != s { - if apdex, err := time.ParseDuration(s + "s"); nil == err { - cfg.ServerlessMode.ApdexThreshold = apdex - } - } - - return cfg -} diff --git a/_integrations/nrlambda/config_test.go b/_integrations/nrlambda/config_test.go deleted file mode 100644 index 8273c5e9d..000000000 --- a/_integrations/nrlambda/config_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrlambda - -import ( - "testing" - "time" -) - -func TestNewConfig(t *testing.T) { - cfg := newConfigInternal(func(key string) string { - switch key { - case "NEW_RELIC_ACCOUNT_ID": - return "the-account-id" - case "NEW_RELIC_TRUSTED_ACCOUNT_KEY": - return "the-trust-key" - case "NEW_RELIC_PRIMARY_APPLICATION_ID": - return "the-app-id" - case "NEW_RELIC_APDEX_T": - return "2" - default: - return "" - } - }) - if !cfg.ServerlessMode.Enabled { - t.Error(cfg.ServerlessMode.Enabled) - } - if cfg.ServerlessMode.AccountID != "the-account-id" { - t.Error(cfg.ServerlessMode.AccountID) - } - if cfg.ServerlessMode.TrustedAccountKey != "the-trust-key" { - t.Error(cfg.ServerlessMode.TrustedAccountKey) - } - if cfg.ServerlessMode.PrimaryAppID != "the-app-id" { - t.Error(cfg.ServerlessMode.PrimaryAppID) - } - if cfg.ServerlessMode.ApdexThreshold != 2*time.Second { - t.Error(cfg.ServerlessMode.ApdexThreshold) - } -} diff --git a/_integrations/nrlambda/events.go b/_integrations/nrlambda/events.go deleted file mode 100644 index e85d54d6c..000000000 --- a/_integrations/nrlambda/events.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrlambda - -import ( - "net/http" - "net/url" - "strings" - - "github.com/aws/aws-lambda-go/events" - newrelic "github.com/newrelic/go-agent" -) - -func getEventSourceARN(event interface{}) string { - switch v := event.(type) { - case events.KinesisFirehoseEvent: - return v.DeliveryStreamArn - case events.KinesisEvent: - if len(v.Records) > 0 { - return v.Records[0].EventSourceArn - } - case events.CodeCommitEvent: - if len(v.Records) > 0 { - return v.Records[0].EventSourceARN - } - case events.DynamoDBEvent: - if len(v.Records) > 0 { - return v.Records[0].EventSourceArn - } - case events.SQSEvent: - if len(v.Records) > 0 { - return v.Records[0].EventSourceARN - } - case events.S3Event: - if len(v.Records) > 0 { - return v.Records[0].S3.Bucket.Arn - } - case events.SNSEvent: - if len(v.Records) > 0 { - return v.Records[0].EventSubscriptionArn - } - } - return "" -} - -type webRequest struct { - header http.Header - method string - u *url.URL - transport newrelic.TransportType -} - -func (r webRequest) Header() http.Header { return r.header } -func (r webRequest) URL() *url.URL { return r.u } -func (r webRequest) Method() string { return r.method } -func (r webRequest) Transport() newrelic.TransportType { return r.transport } - -func eventWebRequest(event interface{}) newrelic.WebRequest { - var path string - var request webRequest - var headers map[string]string - - switch r := event.(type) { - case events.APIGatewayProxyRequest: - request.method = r.HTTPMethod - path = r.Path - headers = r.Headers - case events.ALBTargetGroupRequest: - // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#receive-event-from-load-balancer - request.method = r.HTTPMethod - path = r.Path - headers = r.Headers - default: - return nil - } - - request.header = make(http.Header, len(headers)) - for k, v := range headers { - request.header.Set(k, v) - } - - var host string - if port := request.header.Get("X-Forwarded-Port"); port != "" { - host = ":" + port - } - request.u = &url.URL{ - Path: path, - Host: host, - } - - proto := strings.ToLower(request.header.Get("X-Forwarded-Proto")) - switch proto { - case "https": - request.transport = newrelic.TransportHTTPS - case "http": - request.transport = newrelic.TransportHTTP - default: - request.transport = newrelic.TransportUnknown - } - - return request -} - -func eventResponse(event interface{}) *response { - var code int - var headers map[string]string - - switch r := event.(type) { - case events.APIGatewayProxyResponse: - code = r.StatusCode - headers = r.Headers - case events.ALBTargetGroupResponse: - code = r.StatusCode - headers = r.Headers - default: - return nil - } - hdr := make(http.Header, len(headers)) - for k, v := range headers { - hdr.Add(k, v) - } - return &response{ - code: code, - header: hdr, - } -} diff --git a/_integrations/nrlambda/events_test.go b/_integrations/nrlambda/events_test.go deleted file mode 100644 index 34bc557d4..000000000 --- a/_integrations/nrlambda/events_test.go +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrlambda - -import ( - "testing" - - "github.com/aws/aws-lambda-go/events" - newrelic "github.com/newrelic/go-agent" -) - -func TestGetEventAttributes(t *testing.T) { - testcases := []struct { - Name string - Input interface{} - Arn string - }{ - {Name: "nil", Input: nil, Arn: ""}, - {Name: "SQSEvent empty", Input: events.SQSEvent{}, Arn: ""}, - {Name: "SQSEvent", Input: events.SQSEvent{ - Records: []events.SQSMessage{{ - EventSourceARN: "ARN", - }}, - }, Arn: "ARN"}, - {Name: "SNSEvent empty", Input: events.SNSEvent{}, Arn: ""}, - {Name: "SNSEvent", Input: events.SNSEvent{ - Records: []events.SNSEventRecord{{ - EventSubscriptionArn: "ARN", - }}, - }, Arn: "ARN"}, - {Name: "S3Event empty", Input: events.S3Event{}, Arn: ""}, - {Name: "S3Event", Input: events.S3Event{ - Records: []events.S3EventRecord{{ - S3: events.S3Entity{ - Bucket: events.S3Bucket{ - Arn: "ARN", - }, - }, - }}, - }, Arn: "ARN"}, - {Name: "DynamoDBEvent empty", Input: events.DynamoDBEvent{}, Arn: ""}, - {Name: "DynamoDBEvent", Input: events.DynamoDBEvent{ - Records: []events.DynamoDBEventRecord{{ - EventSourceArn: "ARN", - }}, - }, Arn: "ARN"}, - {Name: "CodeCommitEvent empty", Input: events.CodeCommitEvent{}, Arn: ""}, - {Name: "CodeCommitEvent", Input: events.CodeCommitEvent{ - Records: []events.CodeCommitRecord{{ - EventSourceARN: "ARN", - }}, - }, Arn: "ARN"}, - {Name: "KinesisEvent empty", Input: events.KinesisEvent{}, Arn: ""}, - {Name: "KinesisEvent", Input: events.KinesisEvent{ - Records: []events.KinesisEventRecord{{ - EventSourceArn: "ARN", - }}, - }, Arn: "ARN"}, - {Name: "KinesisFirehoseEvent", Input: events.KinesisFirehoseEvent{ - DeliveryStreamArn: "ARN", - }, Arn: "ARN"}, - } - - for _, testcase := range testcases { - arn := getEventSourceARN(testcase.Input) - if arn != testcase.Arn { - t.Error(testcase.Name, arn, testcase.Arn) - } - } -} - -func TestEventWebRequest(t *testing.T) { - // First test a type that does not count as a web request. - req := eventWebRequest(22) - if nil != req { - t.Error(req) - } - - testcases := []struct { - testname string - input interface{} - numHeaders int - method string - urlString string - transport newrelic.TransportType - }{ - { - testname: "empty proxy request", - input: events.APIGatewayProxyRequest{}, - numHeaders: 0, - method: "", - urlString: "", - transport: newrelic.TransportUnknown, - }, - { - testname: "populated proxy request", - input: events.APIGatewayProxyRequest{ - Headers: map[string]string{ - "x-forwarded-port": "4000", - "x-forwarded-proto": "HTTPS", - }, - HTTPMethod: "GET", - Path: "the/path", - }, - numHeaders: 2, - method: "GET", - urlString: "//:4000/the/path", - transport: newrelic.TransportHTTPS, - }, - { - testname: "empty alb request", - input: events.ALBTargetGroupRequest{}, - numHeaders: 0, - method: "", - urlString: "", - transport: newrelic.TransportUnknown, - }, - { - testname: "populated alb request", - input: events.ALBTargetGroupRequest{ - Headers: map[string]string{ - "x-forwarded-port": "3000", - "x-forwarded-proto": "HttP", - }, - HTTPMethod: "GET", - Path: "the/path", - }, - numHeaders: 2, - method: "GET", - urlString: "//:3000/the/path", - transport: newrelic.TransportHTTP, - }, - } - - for _, tc := range testcases { - req = eventWebRequest(tc.input) - if req == nil { - t.Error(tc.testname, "no request returned") - continue - } - if h := req.Header(); len(h) != tc.numHeaders { - t.Error(tc.testname, "header len mismatch", h, tc.numHeaders) - } - if u := req.URL().String(); u != tc.urlString { - t.Error(tc.testname, "url mismatch", u, tc.urlString) - } - if m := req.Method(); m != tc.method { - t.Error(tc.testname, "method mismatch", m, tc.method) - } - if tr := req.Transport(); tr != tc.transport { - t.Error(tc.testname, "transport mismatch", tr, tc.transport) - } - } -} - -func TestEventResponse(t *testing.T) { - // First test a type that does not count as a web request. - resp := eventResponse(22) - if nil != resp { - t.Error(resp) - } - - testcases := []struct { - testname string - input interface{} - numHeaders int - code int - }{ - { - testname: "empty proxy response", - input: events.APIGatewayProxyResponse{}, - numHeaders: 0, - code: 0, - }, - { - testname: "populated proxy response", - input: events.APIGatewayProxyResponse{ - StatusCode: 200, - Headers: map[string]string{ - "x-custom-header": "my custom header value", - }, - }, - numHeaders: 1, - code: 200, - }, - { - testname: "empty alb response", - input: events.ALBTargetGroupResponse{}, - numHeaders: 0, - code: 0, - }, - { - testname: "populated alb response", - input: events.ALBTargetGroupResponse{ - StatusCode: 200, - Headers: map[string]string{ - "x-custom-header": "my custom header value", - }, - }, - numHeaders: 1, - code: 200, - }, - } - - for _, tc := range testcases { - resp = eventResponse(tc.input) - if resp == nil { - t.Error(tc.testname, "no response returned") - continue - } - if h := resp.Header(); len(h) != tc.numHeaders { - t.Error(tc.testname, "header len mismatch", h, tc.numHeaders) - } - if resp.code != tc.code { - t.Error(tc.testname, "status code mismatch", resp.code, tc.code) - } - } -} diff --git a/_integrations/nrlambda/example/main.go b/_integrations/nrlambda/example/main.go deleted file mode 100644 index a36a3b461..000000000 --- a/_integrations/nrlambda/example/main.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "fmt" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrlambda" -) - -func handler(ctx context.Context) { - // The nrlambda handler instrumentation will add the transaction to the - // context. Access it using newrelic.FromContext to add additional - // instrumentation. - if txn := newrelic.FromContext(ctx); nil != txn { - txn.AddAttribute("userLevel", "gold") - txn.Application().RecordCustomEvent("MyEvent", map[string]interface{}{ - "zip": "zap", - }) - } - fmt.Println("hello world") -} - -func main() { - // nrlambda.NewConfig should be used in place of newrelic.NewConfig - // since it sets Lambda specific configuration settings including - // Config.ServerlessMode.Enabled. - cfg := nrlambda.NewConfig() - // Here is the opportunity to change configuration settings before the - // application is created. - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println("error creating app (invalid config):", err) - } - // nrlambda.Start should be used in place of lambda.Start. - // nrlambda.StartHandler should be used in place of lambda.StartHandler. - nrlambda.Start(handler, app) -} diff --git a/_integrations/nrlambda/handler.go b/_integrations/nrlambda/handler.go deleted file mode 100644 index 5d52cd748..000000000 --- a/_integrations/nrlambda/handler.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrlambda adds support for AWS Lambda. -// -// Use this package to instrument your AWS Lambda handler function. Data is -// sent to CloudWatch when the Lambda is invoked. CloudWatch collects Lambda -// log data and sends it to a New Relic log-ingestion Lambda. The log-ingestion -// Lambda sends that data to us. -// -// Monitoring AWS Lambda requires several steps shown here: -// https://docs.newrelic.com/docs/serverless-function-monitoring/aws-lambda-monitoring/get-started/enable-new-relic-monitoring-aws-lambda -// -// Example: https://github.com/newrelic/go-agent/tree/master/_integrations/nrlambda/example/main.go -package nrlambda - -import ( - "context" - "io" - "net/http" - "os" - "sync" - - "github.com/aws/aws-lambda-go/lambda" - "github.com/aws/aws-lambda-go/lambda/handlertrace" - "github.com/aws/aws-lambda-go/lambdacontext" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -type response struct { - header http.Header - code int -} - -var _ http.ResponseWriter = &response{} - -func (r *response) Header() http.Header { return r.header } -func (r *response) Write([]byte) (int, error) { return 0, nil } -func (r *response) WriteHeader(int) {} - -func requestEvent(ctx context.Context, event interface{}) { - txn := newrelic.FromContext(ctx) - - if nil == txn { - return - } - - if sourceARN := getEventSourceARN(event); "" != sourceARN { - integrationsupport.AddAgentAttribute(txn, internal.AttributeAWSLambdaEventSourceARN, sourceARN, nil) - } - - if request := eventWebRequest(event); nil != request { - txn.SetWebRequest(request) - } -} - -func responseEvent(ctx context.Context, event interface{}) { - txn := newrelic.FromContext(ctx) - if nil == txn { - return - } - if rw := eventResponse(event); nil != rw && 0 != rw.code { - txn.SetWebResponse(rw) - txn.WriteHeader(rw.code) - } -} - -func (h *wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) { - var arn, requestID string - if lctx, ok := lambdacontext.FromContext(ctx); ok { - arn = lctx.InvokedFunctionArn - requestID = lctx.AwsRequestID - } - - defer internal.ServerlessWrite(h.app, arn, h.writer) - - txn := h.app.StartTransaction(h.functionName, nil, nil) - defer txn.End() - - integrationsupport.AddAgentAttribute(txn, internal.AttributeAWSRequestID, requestID, nil) - integrationsupport.AddAgentAttribute(txn, internal.AttributeAWSLambdaARN, arn, nil) - h.firstTransaction.Do(func() { - integrationsupport.AddAgentAttribute(txn, internal.AttributeAWSLambdaColdStart, "", true) - }) - - ctx = newrelic.NewContext(ctx, txn) - ctx = handlertrace.NewContext(ctx, handlertrace.HandlerTrace{ - RequestEvent: requestEvent, - ResponseEvent: responseEvent, - }) - - response, err := h.original.Invoke(ctx, payload) - - if nil != err { - txn.NoticeError(err) - } - - return response, err -} - -type wrappedHandler struct { - original lambda.Handler - app newrelic.Application - // functionName is copied from lambdacontext.FunctionName for - // deterministic tests that don't depend on environment variables. - functionName string - // Although we are told that each Lambda will only handle one request at - // a time, we use a synchronization primitive to determine if this is - // the first transaction for defensiveness in case of future changes. - firstTransaction sync.Once - // writer is used to log the data JSON at the end of each transaction. - // This field exists (rather than hardcoded os.Stdout) for testing. - writer io.Writer -} - -// WrapHandler wraps the provided handler and returns a new handler with -// instrumentation. StartHandler should generally be used in place of -// WrapHandler: this function is exposed for consumers who are chaining -// middlewares. -func WrapHandler(handler lambda.Handler, app newrelic.Application) lambda.Handler { - if nil == app { - return handler - } - return &wrappedHandler{ - original: handler, - app: app, - functionName: lambdacontext.FunctionName, - writer: os.Stdout, - } -} - -// Wrap wraps the provided handler and returns a new handler with -// instrumentation. Start should generally be used in place of Wrap. -func Wrap(handler interface{}, app newrelic.Application) lambda.Handler { - return WrapHandler(lambda.NewHandler(handler), app) -} - -// Start should be used in place of lambda.Start. Replace: -// -// lambda.Start(myhandler) -// -// With: -// -// nrlambda.Start(myhandler, app) -// -func Start(handler interface{}, app newrelic.Application) { - lambda.StartHandler(Wrap(handler, app)) -} - -// StartHandler should be used in place of lambda.StartHandler. Replace: -// -// lambda.StartHandler(myhandler) -// -// With: -// -// nrlambda.StartHandler(myhandler, app) -// -func StartHandler(handler lambda.Handler, app newrelic.Application) { - lambda.StartHandler(WrapHandler(handler, app)) -} diff --git a/_integrations/nrlambda/handler_test.go b/_integrations/nrlambda/handler_test.go deleted file mode 100644 index 59b4006bc..000000000 --- a/_integrations/nrlambda/handler_test.go +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrlambda - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "strings" - "testing" - - "github.com/aws/aws-lambda-go/events" - "github.com/aws/aws-lambda-go/lambdacontext" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" -) - -func dataShouldContain(tb testing.TB, data map[string]json.RawMessage, keys ...string) { - if h, ok := tb.(interface { - Helper() - }); ok { - h.Helper() - } - if len(data) != len(keys) { - tb.Errorf("data key length mismatch, expected=%v got=%v", - len(keys), len(data)) - return - } - for _, k := range keys { - _, ok := data[k] - if !ok { - tb.Errorf("data does not contain key %v", k) - } - } -} - -func testApp(getenv func(string) string, t *testing.T) newrelic.Application { - if nil == getenv { - getenv = func(string) string { return "" } - } - cfg := newConfigInternal(getenv) - - app, err := newrelic.NewApplication(cfg) - if nil != err { - t.Fatal(err) - } - internal.HarvestTesting(app, nil) - return app -} - -func distributedTracingEnabled(key string) string { - switch key { - case "NEW_RELIC_ACCOUNT_ID": - return "1" - case "NEW_RELIC_TRUSTED_ACCOUNT_KEY": - return "1" - case "NEW_RELIC_PRIMARY_APPLICATION_ID": - return "1" - default: - return "" - } -} - -func TestColdStart(t *testing.T) { - originalHandler := func(c context.Context) {} - app := testApp(nil, t) - wrapped := Wrap(originalHandler, app) - w := wrapped.(*wrappedHandler) - w.functionName = "functionName" - buf := &bytes.Buffer{} - w.writer = buf - - ctx := context.Background() - lctx := &lambdacontext.LambdaContext{ - AwsRequestID: "request-id", - InvokedFunctionArn: "function-arn", - } - ctx = lambdacontext.NewContext(ctx, lctx) - - resp, err := wrapped.Invoke(ctx, nil) - if nil != err || string(resp) != "null" { - t.Error("unexpected response", err, string(resp)) - } - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/functionName", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.requestId": "request-id", - "aws.lambda.arn": "function-arn", - "aws.lambda.coldStart": true, - }, - }}) - metadata, data, err := internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Error(err) - } - dataShouldContain(t, data, "metric_data", "analytic_event_data", "span_event_data") - if v := string(metadata["arn"]); v != `"function-arn"` { - t.Error(metadata) - } - - // Invoke the handler again to test the cold-start attribute absence. - buf = &bytes.Buffer{} - w.writer = buf - internal.HarvestTesting(app, nil) - resp, err = wrapped.Invoke(ctx, nil) - if nil != err || string(resp) != "null" { - t.Error("unexpected response", err, string(resp)) - } - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/functionName", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.requestId": "request-id", - "aws.lambda.arn": "function-arn", - }, - }}) - metadata, data, err = internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Error(err) - } - dataShouldContain(t, data, "metric_data", "analytic_event_data", "span_event_data") - if v := string(metadata["arn"]); v != `"function-arn"` { - t.Error(metadata) - } -} - -func TestErrorCapture(t *testing.T) { - returnError := errors.New("problem") - originalHandler := func() error { return returnError } - app := testApp(nil, t) - wrapped := Wrap(originalHandler, app) - w := wrapped.(*wrappedHandler) - w.functionName = "functionName" - buf := &bytes.Buffer{} - w.writer = buf - - resp, err := wrapped.Invoke(context.Background(), nil) - if err != returnError || string(resp) != "" { - t.Error(err, string(resp)) - } - app.(internal.Expect).ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/functionName", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/functionName", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - // Error metrics test the error capture. - {Name: "Errors/all", Scope: "", Forced: true, Data: []float64{1, 0, 0, 0, 0, 0}}, - {Name: "Errors/allOther", Scope: "", Forced: true, Data: []float64{1, 0, 0, 0, 0, 0}}, - {Name: "Errors/OtherTransaction/Go/functionName", Scope: "", Forced: true, Data: []float64{1, 0, 0, 0, 0, 0}}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - }) - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/functionName", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.lambda.coldStart": true, - }, - }}) - _, data, err := internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Error(err) - } - dataShouldContain(t, data, "metric_data", "analytic_event_data", "span_event_data", - "error_event_data", "error_data") -} - -func TestWrapNilApp(t *testing.T) { - originalHandler := func() (int, error) { - return 123, nil - } - wrapped := Wrap(originalHandler, nil) - ctx := context.Background() - resp, err := wrapped.Invoke(ctx, nil) - if nil != err || string(resp) != "123" { - t.Error("unexpected response", err, string(resp)) - } -} - -func TestSetWebRequest(t *testing.T) { - originalHandler := func(events.APIGatewayProxyRequest) {} - app := testApp(nil, t) - wrapped := Wrap(originalHandler, app) - w := wrapped.(*wrappedHandler) - w.functionName = "functionName" - buf := &bytes.Buffer{} - w.writer = buf - - req := events.APIGatewayProxyRequest{ - Headers: map[string]string{ - "X-Forwarded-Port": "4000", - "X-Forwarded-Proto": "HTTPS", - }, - } - reqbytes, err := json.Marshal(req) - if err != nil { - t.Error("unable to marshal json", err) - } - - resp, err := wrapped.Invoke(context.Background(), reqbytes) - if err != nil { - t.Error(err, string(resp)) - } - app.(internal.Expect).ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/functionName", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction/Go/functionName", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/functionName", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - }) - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/functionName", - "nr.apdexPerfZone": "S", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.lambda.coldStart": true, - "request.uri": "//:4000", - }, - }}) - _, data, err := internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Error(err) - } - dataShouldContain(t, data, "metric_data", "analytic_event_data", "span_event_data") -} - -func makePayload(app newrelic.Application) string { - txn := app.StartTransaction("hello", nil, nil) - return txn.CreateDistributedTracePayload().Text() -} - -func TestDistributedTracing(t *testing.T) { - originalHandler := func(events.APIGatewayProxyRequest) {} - app := testApp(distributedTracingEnabled, t) - wrapped := Wrap(originalHandler, app) - w := wrapped.(*wrappedHandler) - w.functionName = "functionName" - buf := &bytes.Buffer{} - w.writer = buf - - req := events.APIGatewayProxyRequest{ - Headers: map[string]string{ - "X-Forwarded-Port": "4000", - "X-Forwarded-Proto": "HTTPS", - newrelic.DistributedTracePayloadHeader: makePayload(app), - }, - } - reqbytes, err := json.Marshal(req) - if err != nil { - t.Error("unable to marshal json", err) - } - - resp, err := wrapped.Invoke(context.Background(), reqbytes) - if err != nil { - t.Error(err, string(resp)) - } - app.(internal.Expect).ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/functionName", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/1/1/HTTPS/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/1/1/HTTPS/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: nil}, - {Name: "TransportDuration/App/1/1/HTTPS/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/1/1/HTTPS/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction/Go/functionName", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/functionName", Scope: "", Forced: false, Data: nil}, - }) - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/functionName", - "nr.apdexPerfZone": "S", - "parent.account": "1", - "parent.app": "1", - "parent.transportType": "HTTPS", - "parent.type": "App", - "guid": internal.MatchAnything, - "parent.transportDuration": internal.MatchAnything, - "parentId": internal.MatchAnything, - "parentSpanId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.lambda.coldStart": true, - "request.uri": "//:4000", - }, - }}) - _, data, err := internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Error(err) - } - dataShouldContain(t, data, "metric_data", "analytic_event_data", "span_event_data") -} - -func TestEventARN(t *testing.T) { - originalHandler := func(events.DynamoDBEvent) {} - app := testApp(nil, t) - wrapped := Wrap(originalHandler, app) - w := wrapped.(*wrappedHandler) - w.functionName = "functionName" - buf := &bytes.Buffer{} - w.writer = buf - - req := events.DynamoDBEvent{ - Records: []events.DynamoDBEventRecord{{ - EventSourceArn: "ARN", - }}, - } - - reqbytes, err := json.Marshal(req) - if err != nil { - t.Error("unable to marshal json", err) - } - - resp, err := wrapped.Invoke(context.Background(), reqbytes) - if err != nil { - t.Error(err, string(resp)) - } - app.(internal.Expect).ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/Go/functionName", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/functionName", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - }) - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/functionName", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.lambda.coldStart": true, - "aws.lambda.eventSource.arn": "ARN", - }, - }}) - _, data, err := internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Error(err) - } - dataShouldContain(t, data, "metric_data", "analytic_event_data", "span_event_data") -} - -func TestAPIGatewayProxyResponse(t *testing.T) { - originalHandler := func() (events.APIGatewayProxyResponse, error) { - return events.APIGatewayProxyResponse{ - Body: "Hello World", - StatusCode: 200, - Headers: map[string]string{ - "Content-Type": "text/html", - }, - }, nil - } - - app := testApp(nil, t) - wrapped := Wrap(originalHandler, app) - w := wrapped.(*wrappedHandler) - w.functionName = "functionName" - buf := &bytes.Buffer{} - w.writer = buf - - resp, err := wrapped.Invoke(context.Background(), nil) - if nil != err { - t.Error("unexpected err", err) - } - if !strings.Contains(string(resp), "Hello World") { - t.Error("unexpected response", string(resp)) - } - - app.(internal.Expect).ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/functionName", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.lambda.coldStart": true, - "httpResponseCode": "200", - "response.headers.contentType": "text/html", - }, - }}) - _, data, err := internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Error(err) - } - dataShouldContain(t, data, "metric_data", "analytic_event_data", "span_event_data") -} - -func TestCustomEvent(t *testing.T) { - originalHandler := func(c context.Context) { - if txn := newrelic.FromContext(c); nil != txn { - txn.Application().RecordCustomEvent("myEvent", map[string]interface{}{ - "zip": "zap", - }) - } - } - app := testApp(nil, t) - wrapped := Wrap(originalHandler, app) - w := wrapped.(*wrappedHandler) - w.functionName = "functionName" - buf := &bytes.Buffer{} - w.writer = buf - - resp, err := wrapped.Invoke(context.Background(), nil) - if nil != err || string(resp) != "null" { - t.Error("unexpected response", err, string(resp)) - } - app.(internal.Expect).ExpectCustomEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "type": "myEvent", - "timestamp": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{ - "zip": "zap", - }, - AgentAttributes: map[string]interface{}{}, - }}) - _, data, err := internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Error(err) - } - dataShouldContain(t, data, "metric_data", "analytic_event_data", "span_event_data", "custom_event_data") -} diff --git a/_integrations/nrlogrus/README.md b/_integrations/nrlogrus/README.md deleted file mode 100644 index cfa195a99..000000000 --- a/_integrations/nrlogrus/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrlogrus [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlogrus?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlogrus) - -Package `nrlogrus` sends go-agent log messages to https://github.com/sirupsen/logrus. - -```go -import "github.com/newrelic/go-agent/_integrations/nrlogrus" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlogrus). diff --git a/_integrations/nrlogrus/example/main.go b/_integrations/nrlogrus/example/main.go deleted file mode 100644 index c44da6f96..000000000 --- a/_integrations/nrlogrus/example/main.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "io" - "net/http" - "os" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrlogrus" - "github.com/sirupsen/logrus" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("Logrus App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - logrus.SetLevel(logrus.DebugLevel) - cfg.Logger = nrlogrus.StandardLogger() - - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - http.HandleFunc(newrelic.WrapHandleFunc(app, "/", func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "hello world") - })) - - http.ListenAndServe(":8000", nil) -} diff --git a/_integrations/nrlogrus/nrlogrus.go b/_integrations/nrlogrus/nrlogrus.go deleted file mode 100644 index 648c1b0e7..000000000 --- a/_integrations/nrlogrus/nrlogrus.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrlogrus sends go-agent log messages to -// https://github.com/sirupsen/logrus. -// -// Use this package if you are using logrus in your application and would like -// the go-agent log messages to end up in the same place. If you are using -// the logrus standard logger, assign the newrelic.Config.Logger field to -// nrlogrus.StandardLogger(): -// -// cfg := newrelic.NewConfig("Your Application Name", "__YOUR_NEW_RELIC_LICENSE_KEY__") -// cfg.Logger = nrlogrus.StandardLogger() -// -// If you are using a particular logrus Logger instance, assign the -// newrelic.Config.Logger field to the the output of nrlogrus.Transform: -// -// l := logrus.New() -// l.SetLevel(logrus.DebugLevel) -// cfg := newrelic.NewConfig("Your Application Name", "__YOUR_NEW_RELIC_LICENSE_KEY__") -// cfg.Logger = nrlogrus.Transform(l) -// -// This package requires logrus version v1.1.0 and above. -package nrlogrus - -import ( - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/sirupsen/logrus" -) - -func init() { internal.TrackUsage("integration", "logging", "logrus") } - -type shim struct { - e *logrus.Entry - l *logrus.Logger -} - -func (s *shim) Error(msg string, c map[string]interface{}) { - s.e.WithFields(c).Error(msg) -} -func (s *shim) Warn(msg string, c map[string]interface{}) { - s.e.WithFields(c).Warn(msg) -} -func (s *shim) Info(msg string, c map[string]interface{}) { - s.e.WithFields(c).Info(msg) -} -func (s *shim) Debug(msg string, c map[string]interface{}) { - s.e.WithFields(c).Debug(msg) -} -func (s *shim) DebugEnabled() bool { - lvl := s.l.GetLevel() - return lvl >= logrus.DebugLevel -} - -// StandardLogger returns a newrelic.Logger which forwards agent log messages to -// the logrus package-level exported logger. -func StandardLogger() newrelic.Logger { - return Transform(logrus.StandardLogger()) -} - -// Transform turns a *logrus.Logger into a newrelic.Logger. -func Transform(l *logrus.Logger) newrelic.Logger { - return &shim{ - l: l, - e: l.WithFields(logrus.Fields{ - "component": "newrelic", - }), - } -} diff --git a/_integrations/nrlogrus/nrlogrus_test.go b/_integrations/nrlogrus/nrlogrus_test.go deleted file mode 100644 index e9fe3ac33..000000000 --- a/_integrations/nrlogrus/nrlogrus_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrlogrus - -import ( - "bytes" - "strings" - "testing" - - "github.com/sirupsen/logrus" -) - -func bufferToStringAndReset(buf *bytes.Buffer) string { - s := buf.String() - buf.Reset() - return s -} - -func TestLogrus(t *testing.T) { - buf := &bytes.Buffer{} - l := logrus.New() - l.SetOutput(buf) - l.SetLevel(logrus.DebugLevel) - lg := Transform(l) - lg.Debug("elephant", map[string]interface{}{"color": "gray"}) - s := bufferToStringAndReset(buf) - if !strings.Contains(s, "elephant") || !strings.Contains(s, "gray") { - t.Error(s) - } - if enabled := lg.DebugEnabled(); !enabled { - t.Error(enabled) - } - // Now switch the level and test that debug is no longer enabled. - l.SetLevel(logrus.InfoLevel) - lg.Debug("lion", map[string]interface{}{"color": "yellow"}) - s = bufferToStringAndReset(buf) - if strings.Contains(s, "lion") || strings.Contains(s, "yellow") { - t.Error(s) - } - if enabled := lg.DebugEnabled(); enabled { - t.Error(enabled) - } - lg.Info("tiger", map[string]interface{}{"color": "orange"}) - s = bufferToStringAndReset(buf) - if !strings.Contains(s, "tiger") || !strings.Contains(s, "orange") { - t.Error(s) - } -} diff --git a/_integrations/nrlogxi/v1/README.md b/_integrations/nrlogxi/v1/README.md deleted file mode 100644 index 54244d0cc..000000000 --- a/_integrations/nrlogxi/v1/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrlogxi/v1 [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlogxi/v1?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlogxi/v1) - -Package `nrlogxi` supports https://github.com/mgutz/logxi. - -```go -import "github.com/newrelic/go-agent/_integrations/nrlogxi/v1" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlogxi/v1). diff --git a/_integrations/nrlogxi/v1/example_test.go b/_integrations/nrlogxi/v1/example_test.go deleted file mode 100644 index d2f959f78..000000000 --- a/_integrations/nrlogxi/v1/example_test.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrlogxi_test - -import ( - log "github.com/mgutz/logxi/v1" - newrelic "github.com/newrelic/go-agent" - nrlogxi "github.com/newrelic/go-agent/_integrations/nrlogxi/v1" -) - -func Example() { - cfg := newrelic.NewConfig("Example App", "__YOUR_NEWRELIC_LICENSE_KEY__") - - // Create a new logxi logger: - l := log.New("newrelic") - l.SetLevel(log.LevelInfo) - - // Use nrlogxi to register the logger with the agent: - cfg.Logger = nrlogxi.New(l) - - newrelic.NewApplication(cfg) -} diff --git a/_integrations/nrlogxi/v1/nrlogxi.go b/_integrations/nrlogxi/v1/nrlogxi.go deleted file mode 100644 index 85a590542..000000000 --- a/_integrations/nrlogxi/v1/nrlogxi.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrlogxi supports https://github.com/mgutz/logxi. -// -// Wrap your logxi Logger using nrlogxi.New to send agent log messages through -// logxi. -package nrlogxi - -import ( - "github.com/mgutz/logxi/v1" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" -) - -func init() { internal.TrackUsage("integration", "logging", "logxi", "v1") } - -type shim struct { - e log.Logger -} - -func (l *shim) Error(msg string, context map[string]interface{}) { - l.e.Error(msg, convert(context)...) -} -func (l *shim) Warn(msg string, context map[string]interface{}) { - l.e.Warn(msg, convert(context)...) -} -func (l *shim) Info(msg string, context map[string]interface{}) { - l.e.Info(msg, convert(context)...) -} -func (l *shim) Debug(msg string, context map[string]interface{}) { - l.e.Debug(msg, convert(context)...) -} -func (l *shim) DebugEnabled() bool { - return l.e.IsDebug() -} - -func convert(c map[string]interface{}) []interface{} { - output := make([]interface{}, 0, 2*len(c)) - for k, v := range c { - output = append(output, k, v) - } - return output -} - -// New returns a newrelic.Logger which forwards agent log messages to the -// provided logxi Logger. -func New(l log.Logger) newrelic.Logger { - return &shim{ - e: l, - } -} diff --git a/_integrations/nrmicro/README.md b/_integrations/nrmicro/README.md deleted file mode 100644 index 371bb12b1..000000000 --- a/_integrations/nrmicro/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrmicro [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrmicro?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrmicro) - -Package `nrmicro` instruments https://github.com/micro/go-micro. - -```go -import "github.com/newrelic/go-agent/_integrations/nrmicro" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrmicro). diff --git a/_integrations/nrmicro/example/README.md b/_integrations/nrmicro/example/README.md deleted file mode 100644 index f2981c866..000000000 --- a/_integrations/nrmicro/example/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Example Go Micro apps -In this directory you will find several example Go Micro apps that are instrumented using the New Relic agent. All of the apps assume that your New Relic license key is available as an environment variable named `NEW_RELIC_LICENSE_KEY` - -They can be run the standard way: -* The sample Pub/Sub app: `go run pubsub/main.go` instruments both a publish and a subscribe method -* The sample Server app: `go run server/server.go` instruments a handler method -* The sample Client app: `go run client/client.go` instruments the client. - * Note that in order for this to function, the server app must also be running. - \ No newline at end of file diff --git a/_integrations/nrmicro/example/client/client.go b/_integrations/nrmicro/example/client/client.go deleted file mode 100644 index cc10a5fb5..000000000 --- a/_integrations/nrmicro/example/client/client.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "fmt" - "os" - "time" - - "github.com/micro/go-micro" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrmicro" - proto "github.com/newrelic/go-agent/_integrations/nrmicro/example/proto" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("Micro Client", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - err = app.WaitForConnection(10 * time.Second) - if nil != err { - panic(err) - } - defer app.Shutdown(10 * time.Second) - - txn := app.StartTransaction("client", nil, nil) - defer txn.End() - - service := micro.NewService( - // Add the New Relic wrapper to the client which will create External - // segments for each out going call. - micro.WrapClient(nrmicro.ClientWrapper()), - ) - service.Init() - ctx := newrelic.NewContext(context.Background(), txn) - c := proto.NewGreeterService("greeter", service.Client()) - - rsp, err := c.Hello(ctx, &proto.HelloRequest{ - Name: "John", - }) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(rsp.Greeting) -} diff --git a/_integrations/nrmicro/example/proto/greeter.micro.go b/_integrations/nrmicro/example/proto/greeter.micro.go deleted file mode 100644 index 2b25211be..000000000 --- a/_integrations/nrmicro/example/proto/greeter.micro.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by protoc-gen-micro. DO NOT EDIT. -// source: greeter.proto - -package greeter - -import ( - fmt "fmt" - proto "github.com/golang/protobuf/proto" - math "math" -) - -import ( - context "context" - client "github.com/micro/go-micro/client" - server "github.com/micro/go-micro/server" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ client.Option -var _ server.Option - -// Client API for Greeter service - -type GreeterService interface { - Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) -} - -type greeterService struct { - c client.Client - name string -} - -func NewGreeterService(name string, c client.Client) GreeterService { - if c == nil { - c = client.NewClient() - } - if len(name) == 0 { - name = "greeter" - } - return &greeterService{ - c: c, - name: name, - } -} - -func (c *greeterService) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) { - req := c.c.NewRequest(c.name, "Greeter.Hello", in) - out := new(HelloResponse) - err := c.c.Call(ctx, req, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for Greeter service - -type GreeterHandler interface { - Hello(context.Context, *HelloRequest, *HelloResponse) error -} - -func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler, opts ...server.HandlerOption) error { - type greeter interface { - Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error - } - type Greeter struct { - greeter - } - h := &greeterHandler{hdlr} - return s.Handle(s.NewHandler(&Greeter{h}, opts...)) -} - -type greeterHandler struct { - GreeterHandler -} - -func (h *greeterHandler) Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error { - return h.GreeterHandler.Hello(ctx, in, out) -} diff --git a/_integrations/nrmicro/example/proto/greeter.pb.go b/_integrations/nrmicro/example/proto/greeter.pb.go deleted file mode 100644 index 0b1ea5f82..000000000 --- a/_integrations/nrmicro/example/proto/greeter.pb.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: greeter.proto - -package greeter - -import ( - fmt "fmt" - proto "github.com/golang/protobuf/proto" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package - -type HelloRequest struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *HelloRequest) Reset() { *m = HelloRequest{} } -func (m *HelloRequest) String() string { return proto.CompactTextString(m) } -func (*HelloRequest) ProtoMessage() {} -func (*HelloRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e585294ab3f34af5, []int{0} -} - -func (m *HelloRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_HelloRequest.Unmarshal(m, b) -} -func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic) -} -func (m *HelloRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_HelloRequest.Merge(m, src) -} -func (m *HelloRequest) XXX_Size() int { - return xxx_messageInfo_HelloRequest.Size(m) -} -func (m *HelloRequest) XXX_DiscardUnknown() { - xxx_messageInfo_HelloRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_HelloRequest proto.InternalMessageInfo - -func (m *HelloRequest) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -type HelloResponse struct { - Greeting string `protobuf:"bytes,2,opt,name=greeting,proto3" json:"greeting,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *HelloResponse) Reset() { *m = HelloResponse{} } -func (m *HelloResponse) String() string { return proto.CompactTextString(m) } -func (*HelloResponse) ProtoMessage() {} -func (*HelloResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e585294ab3f34af5, []int{1} -} - -func (m *HelloResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_HelloResponse.Unmarshal(m, b) -} -func (m *HelloResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_HelloResponse.Marshal(b, m, deterministic) -} -func (m *HelloResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_HelloResponse.Merge(m, src) -} -func (m *HelloResponse) XXX_Size() int { - return xxx_messageInfo_HelloResponse.Size(m) -} -func (m *HelloResponse) XXX_DiscardUnknown() { - xxx_messageInfo_HelloResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_HelloResponse proto.InternalMessageInfo - -func (m *HelloResponse) GetGreeting() string { - if m != nil { - return m.Greeting - } - return "" -} - -func init() { - proto.RegisterType((*HelloRequest)(nil), "HelloRequest") - proto.RegisterType((*HelloResponse)(nil), "HelloResponse") -} - -func init() { proto.RegisterFile("greeter.proto", fileDescriptor_e585294ab3f34af5) } - -var fileDescriptor_e585294ab3f34af5 = []byte{ - // 130 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4d, 0x2f, 0x4a, 0x4d, - 0x2d, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x57, 0x52, 0xe2, 0xe2, 0xf1, 0x48, 0xcd, - 0xc9, 0xc9, 0x0f, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, - 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0xb4, 0xb9, 0x78, 0xa1, 0x6a, - 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xa4, 0xb8, 0x38, 0xc0, 0xa6, 0x64, 0xe6, 0xa5, 0x4b, - 0x30, 0x81, 0x15, 0xc2, 0xf9, 0x46, 0xc6, 0x5c, 0xec, 0xee, 0x10, 0x1b, 0x84, 0x34, 0xb8, 0x58, - 0xc1, 0xfa, 0x84, 0x78, 0xf5, 0x90, 0xed, 0x90, 0xe2, 0xd3, 0x43, 0x31, 0x4e, 0x89, 0x21, 0x89, - 0x0d, 0xec, 0x18, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbd, 0xe0, 0x75, 0x0a, 0x9d, 0x00, - 0x00, 0x00, -} diff --git a/_integrations/nrmicro/example/proto/greeter.proto b/_integrations/nrmicro/example/proto/greeter.proto deleted file mode 100644 index dc4ff3b19..000000000 --- a/_integrations/nrmicro/example/proto/greeter.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -syntax = "proto3"; - -service Greeter { - rpc Hello(HelloRequest) returns (HelloResponse) {} -} - -message HelloRequest { - string name = 1; -} - -message HelloResponse { - string greeting = 2; -} diff --git a/_integrations/nrmicro/example/pubsub/main.go b/_integrations/nrmicro/example/pubsub/main.go deleted file mode 100644 index c6971a198..000000000 --- a/_integrations/nrmicro/example/pubsub/main.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "fmt" - "log" - "os" - "time" - - "github.com/micro/go-micro" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrmicro" - proto "github.com/newrelic/go-agent/_integrations/nrmicro/example/proto" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func subEv(ctx context.Context, msg *proto.HelloRequest) error { - fmt.Println("Message received from", msg.GetName()) - return nil -} - -func publish(s micro.Service, app newrelic.Application) { - c := s.Client() - - for range time.NewTicker(time.Second).C { - txn := app.StartTransaction("publish", nil, nil) - msg := c.NewMessage("example.topic.pubsub", &proto.HelloRequest{Name: "Sally"}) - ctx := newrelic.NewContext(context.Background(), txn) - fmt.Println("Sending message") - if err := c.Publish(ctx, msg); nil != err { - log.Fatal(err) - } - txn.End() - } -} - -func main() { - cfg := newrelic.NewConfig("Micro Pub/Sub", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - err = app.WaitForConnection(10 * time.Second) - if nil != err { - panic(err) - } - defer app.Shutdown(10 * time.Second) - - s := micro.NewService( - micro.Name("go.micro.srv.pubsub"), - // Add the New Relic wrapper to the client which will create - // MessageProducerSegments for each Publish call. - micro.WrapClient(nrmicro.ClientWrapper()), - // Add the New Relic wrapper to the subscriber which will start a new - // transaction for each Subscriber invocation. - micro.WrapSubscriber(nrmicro.SubscriberWrapper(app)), - ) - s.Init() - - go publish(s, app) - - micro.RegisterSubscriber("example.topic.pubsub", s.Server(), subEv) - - if err := s.Run(); err != nil { - log.Fatal(err) - } -} diff --git a/_integrations/nrmicro/example/server/server.go b/_integrations/nrmicro/example/server/server.go deleted file mode 100644 index f56dfcf96..000000000 --- a/_integrations/nrmicro/example/server/server.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "fmt" - "log" - "os" - "time" - - "github.com/micro/go-micro" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrmicro" - proto "github.com/newrelic/go-agent/_integrations/nrmicro/example/proto" -) - -// Greeter is the server struct -type Greeter struct{} - -// Hello is the method on the server being called -func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error { - name := req.GetName() - if txn := newrelic.FromContext(ctx); nil != txn { - txn.AddAttribute("Name", name) - } - fmt.Println("Request received from", name) - rsp.Greeting = "Hello " + name - return nil -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("Micro Server", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - err = app.WaitForConnection(10 * time.Second) - if nil != err { - panic(err) - } - defer app.Shutdown(10 * time.Second) - - service := micro.NewService( - micro.Name("greeter"), - // Add the New Relic middleware which will start a new transaction for - // each Handler invocation. - micro.WrapHandler(nrmicro.HandlerWrapper(app)), - ) - - service.Init() - - proto.RegisterGreeterHandler(service.Server(), new(Greeter)) - - if err := service.Run(); err != nil { - log.Fatal(err) - } -} diff --git a/_integrations/nrmicro/nrmicro.go b/_integrations/nrmicro/nrmicro.go deleted file mode 100644 index a4ce3319d..000000000 --- a/_integrations/nrmicro/nrmicro.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrmicro - -import ( - "context" - "net/http" - "net/url" - "strings" - - "github.com/micro/go-micro/client" - "github.com/micro/go-micro/errors" - "github.com/micro/go-micro/metadata" - "github.com/micro/go-micro/registry" - "github.com/micro/go-micro/server" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -type nrWrapper struct { - client.Client -} - -var addrMap = make(map[string]string) - -func startExternal(ctx context.Context, procedure, host string) (context.Context, newrelic.ExternalSegment) { - var seg newrelic.ExternalSegment - if txn := newrelic.FromContext(ctx); nil != txn { - seg = newrelic.ExternalSegment{ - StartTime: newrelic.StartSegmentNow(txn), - Procedure: procedure, - Library: "Micro", - Host: host, - } - ctx = addDTPayloadToContext(ctx, txn) - } - return ctx, seg -} - -func startMessage(ctx context.Context, topic string) (context.Context, *newrelic.MessageProducerSegment) { - var seg *newrelic.MessageProducerSegment - if txn := newrelic.FromContext(ctx); nil != txn { - seg = &newrelic.MessageProducerSegment{ - StartTime: newrelic.StartSegmentNow(txn), - Library: "Micro", - DestinationType: newrelic.MessageTopic, - DestinationName: topic, - } - ctx = addDTPayloadToContext(ctx, txn) - } - return ctx, seg -} - -func addDTPayloadToContext(ctx context.Context, txn newrelic.Transaction) context.Context { - payload := txn.CreateDistributedTracePayload() - if txt := payload.Text(); "" != txt { - md, _ := metadata.FromContext(ctx) - md = metadata.Copy(md) - md[newrelic.DistributedTracePayloadHeader] = txt - ctx = metadata.NewContext(ctx, md) - } - return ctx -} - -func extractHost(addr string) string { - if host, ok := addrMap[addr]; ok { - return host - } - - host := addr - if strings.HasPrefix(host, "unix://") { - host = "localhost" - } else if u, err := url.Parse(host); nil == err { - if "" != u.Host { - host = u.Host - } else { - host = u.Path - } - } - - addrMap[addr] = host - return host -} - -func (n *nrWrapper) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error { - ctx, seg := startMessage(ctx, msg.Topic()) - defer seg.End() - return n.Client.Publish(ctx, msg, opts...) -} - -func (n *nrWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { - ctx, seg := startExternal(ctx, req.Endpoint(), req.Service()) - defer seg.End() - return n.Client.Stream(ctx, req, opts...) -} - -func (n *nrWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { - ctx, seg := startExternal(ctx, req.Endpoint(), req.Service()) - defer seg.End() - return n.Client.Call(ctx, req, rsp, opts...) -} - -// ClientWrapper wraps a Micro `client.Client` -// (https://godoc.org/github.com/micro/go-micro/client#Client) instance. External -// segments will be created for each call to the client's `Call`, `Publish`, or -// `Stream` methods. The `newrelic.Transaction` must be put into the context -// using `newrelic.NewContext` -// (https://godoc.org/github.com/newrelic/go-agent#NewContext) when calling one -// of those methods. -func ClientWrapper() client.Wrapper { - return func(c client.Client) client.Client { - return &nrWrapper{c} - } -} - -// CallWrapper wraps the `Call` method of a Micro `client.Client` -// (https://godoc.org/github.com/micro/go-micro/client#Client) instance. -// External segments will be created for each call to the client's `Call` -// method. The `newrelic.Transaction` must be put into the context using -// `newrelic.NewContext` -// (https://godoc.org/github.com/newrelic/go-agent#NewContext) when calling -// `Call`. -func CallWrapper() client.CallWrapper { - return func(cf client.CallFunc) client.CallFunc { - return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error { - ctx, seg := startExternal(ctx, req.Endpoint(), req.Service()) - defer seg.End() - return cf(ctx, node, req, rsp, opts) - } - } -} - -// HandlerWrapper wraps a Micro `server.Server` -// (https://godoc.org/github.com/micro/go-micro/server#Server) handler. -// -// This wrapper creates transactions for inbound calls. The transaction is -// added to the call context and can be accessed in your method handlers using -// `newrelic.FromContext` -// (https://godoc.org/github.com/newrelic/go-agent#FromContext). -// -// When an error is returned and it is of type Micro `errors.Error` -// (https://godoc.org/github.com/micro/go-micro/errors#Error), the error that -// is recorded is based on the HTTP response code (found in the Code field). -// Values above 400 or below 100 that are not in the IgnoreStatusCodes -// (https://godoc.org/github.com/newrelic/go-agent#Config) configuration list -// are recorded as errors. A 500 response code and corresponding error is -// recorded when the error is of any other type. A 200 response code is -// recorded if no error is returned. -func HandlerWrapper(app newrelic.Application) server.HandlerWrapper { - return func(fn server.HandlerFunc) server.HandlerFunc { - if app == nil { - return fn - } - return func(ctx context.Context, req server.Request, rsp interface{}) error { - txn := startWebTransaction(ctx, app, req) - defer txn.End() - err := fn(newrelic.NewContext(ctx, txn), req, rsp) - var code int - if err != nil { - if t, ok := err.(*errors.Error); ok { - code = int(t.Code) - } else { - code = 500 - } - } else { - code = 200 - } - txn.WriteHeader(code) - return err - } - } -} - -// SubscriberWrapper wraps a Micro `server.Subscriber` -// (https://godoc.org/github.com/micro/go-micro/server#Subscriber) instance. -// -// This wrapper creates background transactions for inbound calls. The -// transaction is added to the subscriber context and can be accessed in your -// subscriber handlers using `newrelic.FromContext` -// (https://godoc.org/github.com/newrelic/go-agent#FromContext). -// -// The attribute `"message.routingKey"` is added to the transaction and will -// appear on transaction events, transaction traces, error events, and error -// traces. It corresponds to the `server.Message`'s Topic -// (https://godoc.org/github.com/micro/go-micro/server#Message). -// -// If a Subscriber returns an error, it will be recorded and reported. -func SubscriberWrapper(app newrelic.Application) server.SubscriberWrapper { - return func(fn server.SubscriberFunc) server.SubscriberFunc { - if app == nil { - return fn - } - return func(ctx context.Context, m server.Message) (err error) { - namer := internal.MessageMetricKey{ - Library: "Micro", - DestinationType: string(newrelic.MessageTopic), - DestinationName: m.Topic(), - Consumer: true, - } - txn := app.StartTransaction(namer.Name(), nil, nil) - defer txn.End() - integrationsupport.AddAgentAttribute(txn, internal.AttributeMessageRoutingKey, m.Topic(), nil) - md, ok := metadata.FromContext(ctx) - if ok { - txn.AcceptDistributedTracePayload(newrelic.TransportHTTP, md[newrelic.DistributedTracePayloadHeader]) - } - ctx = newrelic.NewContext(ctx, txn) - err = fn(ctx, m) - if err != nil { - txn.NoticeError(err) - } - return err - } - } -} - -func startWebTransaction(ctx context.Context, app newrelic.Application, req server.Request) newrelic.Transaction { - var hdrs http.Header - if md, ok := metadata.FromContext(ctx); ok { - hdrs = make(http.Header, len(md)) - for k, v := range md { - hdrs.Add(k, v) - } - } - txn := app.StartTransaction(req.Endpoint(), nil, nil) - u := &url.URL{ - Scheme: "micro", - Host: req.Service(), - Path: req.Endpoint(), - } - - webReq := newrelic.NewStaticWebRequest(hdrs, u, req.Method(), newrelic.TransportHTTP) - txn.SetWebRequest(webReq) - - return txn -} diff --git a/_integrations/nrmicro/nrmicro_doc.go b/_integrations/nrmicro/nrmicro_doc.go deleted file mode 100644 index 585e1cf9e..000000000 --- a/_integrations/nrmicro/nrmicro_doc.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrmicro instruments https://github.com/micro/go-micro. -// -// This package can be used to instrument Micro Servers, Clients, Producers, -// and Subscribers. -// -// Micro Servers -// -// To instrument a Micro Server, use the `micro.WrapHandler` -// (https://godoc.org/github.com/micro/go-micro#WrapHandler) option with -// `nrmicro.HandlerWrapper` and your `newrelic.Application` and pass it to the -// `micro.NewService` method. Example: -// -// cfg := newrelic.NewConfig("Micro Server", os.Getenv("NEW_RELIC_LICENSE_KEY")) -// app, _ := newrelic.NewApplication(cfg) -// service := micro.NewService( -// micro.WrapHandler(nrmicro.HandlerWrapper(app)), -// ) -// -// Alternatively, use the `server.WrapHandler` -// (https://godoc.org/github.com/micro/go-micro/server#WrapHandler) option with -// `nrmicro.HandlerWrapper` and your `newrelic.Application` and pass it to the -// `server.NewServer` method. Example: -// -// cfg := newrelic.NewConfig("Micro Server", os.Getenv("NEW_RELIC_LICENSE_KEY")) -// app, _ := newrelic.NewApplication(cfg) -// svr := server.NewServer( -// server.WrapHandler(nrmicro.HandlerWrapper(app)), -// ) -// -// If more than one wrapper is passed to `micro.WrapHandler` or -// `server.WrapHandler` as a list, be sure that the `nrmicro.HandlerWrapper` is -// first in this list. -// -// This wrapper creates transactions for inbound calls. The transaction is -// added to the call context and can be accessed in your method handlers using -// `newrelic.FromContext` -// (https://godoc.org/github.com/newrelic/go-agent#FromContext). -// -// When an error is returned and it is of type Micro `errors.Error` -// (https://godoc.org/github.com/micro/go-micro/errors#Error), the error that -// is recorded is based on the HTTP response code (found in the Code field). -// Values above 400 or below 100 that are not in the IgnoreStatusCodes -// (https://godoc.org/github.com/newrelic/go-agent#Config) configuration list -// are recorded as errors. A 500 response code and corresponding error is -// recorded when the error is of any other type. A 200 response code is -// recorded if no error is returned. -// -// Full server example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrmicro/example/server/server.go -// -// Micro Clients -// -// There are three different ways to instrument a Micro Client and create -// External segments for `Call`, `Publish`, and `Stream` methods. -// -// No matter which way the Micro `client.Client` is wrapped, all calls to -// `Client.Call`, `Client.Publish`, or `Client.Stream` must be done with a -// context which contains a `newrelic.Transaction`. -// -// ctx = newrelic.NewContext(ctx, txn) -// err := cli.Call(ctx, req, &rsp) -// -// 1. The first option is to wrap the `Call`, `Publish`, and `Stream` methods -// on a client using the `micro.WrapClient` -// (https://godoc.org/github.com/micro/go-micro#WrapClient) option with -// `nrmicro.ClientWrapper` and pass it to the `micro.NewService` method. If -// more than one wrapper is passed to `micro.WrapClient`, ensure that the -// `nrmicro.ClientWrapper` is the first in the list. `ExternalSegment`s will be -// created each time a `Call` or `Stream` method is called on the -// client. `MessageProducerSegment`s will be created each time a `Publish` -// method is called on the client. Example: -// -// service := micro.NewService( -// micro.WrapClient(nrmicro.ClientWrapper()), -// ) -// cli := service.Client() -// -// It is also possible to use the `client.Wrap` -// (https://godoc.org/github.com/micro/go-micro/client#Wrap) option with -// `nrmicro.ClientWrapper` and pass it to the `client.NewClient` method to -// achieve the same result. -// -// cli := client.NewClient( -// client.Wrap(nrmicro.ClientWrapper()), -// ) -// -// 2. The second option is to wrap just the `Call` method on a client using the -// `micro.WrapCall` (https://godoc.org/github.com/micro/go-micro#WrapCall) -// option with `nrmicro.CallWrapper` and pass it to the `micro.NewService` -// method. If more than one wrapper is passed to `micro.WrapCall`, ensure that -// the `nrmicro.CallWrapper` is the first in the list. External segments will -// be created each time a `Call` method is called on the client. Example: -// -// service := micro.NewService( -// micro.WrapCall(nrmicro.CallWrapper()), -// ) -// cli := service.Client() -// -// It is also possible to use the `client.WrapCall` -// (https://godoc.org/github.com/micro/go-micro/client#WrapCall) option with -// `nrmicro.CallWrapper` and pass it to the `client.NewClient` method to -// achieve the same result. -// -// cli := client.NewClient( -// client.WrapCall(nrmicro.CallWrapper()), -// ) -// -// 3. The third option is to wrap the Micro Client directly using -// `nrmicro.ClientWrapper`. `ExternalSegment`s will be created each time a -// `Call` or `Stream` method is called on the client. -// `MessageProducerSegment`s will be created each time a `Publish` method is -// called on the client. Example: -// -// cli := client.NewClient() -// cli = nrmicro.ClientWrapper()(cli) -// -// Full client example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrmicro/example/client/client.go -// -// Micro Producers -// -// To instrument a Micro Producer, wrap the Micro Client using the -// `nrmico.ClientWrapper` as described in option 1 or 3 above. -// `MessageProducerSegment`s will be created each time a `Publish` method is -// called on the client. Be sure the context passed to the `Publish` method -// contains a `newrelic.Transaction`. -// -// service := micro.NewService( -// micro.WrapClient(nrmicro.ClientWrapper()), -// ) -// cli := service.Client() -// -// // Add the transaction to the context -// ctx := newrelic.NewContext(context.Background(), txn) -// msg := cli.NewMessage("my.example.topic", "hello world") -// err := cli.Publish(ctx, msg) -// -// Full Publisher/Subscriber example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrmicro/example/pubsub/main.go -// -// Micro Subscribers -// -// To instrument a Micro Subscriber use the `micro.WrapSubscriber` -// (https://godoc.org/github.com/micro/go-micro#WrapSubscriber) option with -// `nrmicro.SubscriberWrapper` and your `newrelic.Application` and pass it to -// the `micro.NewService` method. Example: -// -// cfg := newrelic.NewConfig("Micro Subscriber", os.Getenv("NEW_RELIC_LICENSE_KEY")) -// app, _ := newrelic.NewApplication(cfg) -// service := micro.NewService( -// micro.WrapSubscriber(nrmicro.SubscriberWrapper(app)), -// ) -// -// Alternatively, use the `server.WrapSubscriber` -// (https://godoc.org/github.com/micro/go-micro/server#WrapSubscriber) option -// with `nrmicro.SubscriberWrapper` and your `newrelic.Application` and pass it -// to the `server.NewServer` method. Example: -// -// cfg := newrelic.NewConfig("Micro Subscriber", os.Getenv("NEW_RELIC_LICENSE_KEY")) -// app, _ := newrelic.NewApplication(cfg) -// svr := server.NewServer( -// server.WrapSubscriber(nrmicro.SubscriberWrapper(app)), -// ) -// -// If more than one wrapper is passed to `micro.WrapSubscriber` or -// `server.WrapSubscriber` as a list, be sure that the `nrmicro.SubscriberWrapper` is -// first in this list. -// -// This wrapper creates background transactions for inbound calls. The -// transaction is added to the subscriber context and can be accessed in your -// subscriber handlers using `newrelic.FromContext`. -// -// If a Subscriber returns an error, it will be recorded and reported. -// -// Full Publisher/Subscriber example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrmicro/example/pubsub/main.go -package nrmicro - -import "github.com/newrelic/go-agent/internal" - -func init() { internal.TrackUsage("integration", "framework", "micro") } diff --git a/_integrations/nrmicro/nrmicro_test.go b/_integrations/nrmicro/nrmicro_test.go deleted file mode 100644 index 0cd160a6f..000000000 --- a/_integrations/nrmicro/nrmicro_test.go +++ /dev/null @@ -1,1034 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrmicro - -import ( - "context" - "errors" - "sync" - "testing" - "time" - - "github.com/micro/go-micro" - "github.com/micro/go-micro/broker" - bmemory "github.com/micro/go-micro/broker/memory" - "github.com/micro/go-micro/client" - "github.com/micro/go-micro/client/selector" - microerrors "github.com/micro/go-micro/errors" - "github.com/micro/go-micro/metadata" - rmemory "github.com/micro/go-micro/registry/memory" - "github.com/micro/go-micro/server" - newrelic "github.com/newrelic/go-agent" - proto "github.com/newrelic/go-agent/_integrations/nrmicro/example/proto" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -const ( - missingHeaders = "HEADERS NOT FOUND" - missingMetadata = "METADATA NOT FOUND" - serverName = "testing" - topic = "topic" -) - -type TestRequest struct{} - -type TestResponse struct { - RequestHeaders string -} - -func dtHeadersFound(hdr string) bool { - return hdr != "" && hdr != missingMetadata && hdr != missingHeaders -} - -type TestHandler struct{} - -func (t *TestHandler) Method(ctx context.Context, req *TestRequest, rsp *TestResponse) error { - rsp.RequestHeaders = getDTRequestHeaderVal(ctx) - defer newrelic.StartSegment(newrelic.FromContext(ctx), "Method").End() - return nil -} - -func (t *TestHandler) StreamingMethod(ctx context.Context, stream server.Stream) error { - if err := stream.Send(getDTRequestHeaderVal(ctx)); nil != err { - return err - } - return nil -} - -type TestHandlerWithError struct{} - -func (t *TestHandlerWithError) Method(ctx context.Context, req *TestRequest, rsp *TestResponse) error { - rsp.RequestHeaders = getDTRequestHeaderVal(ctx) - return microerrors.Unauthorized("id", "format") -} - -type TestHandlerWithNonMicroError struct{} - -func (t *TestHandlerWithNonMicroError) Method(ctx context.Context, req *TestRequest, rsp *TestResponse) error { - rsp.RequestHeaders = getDTRequestHeaderVal(ctx) - return errors.New("Non-Micro Error") -} - -func getDTRequestHeaderVal(ctx context.Context) string { - if md, ok := metadata.FromContext(ctx); ok { - if dtHeader, ok := md[newrelic.DistributedTracePayloadHeader]; ok { - return dtHeader - } - return missingHeaders - } - return missingMetadata -} - -func createTestApp() integrationsupport.ExpectApp { - return integrationsupport.NewTestApp(replyFn, cfgFn) -} - -var replyFn = func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.AccountID = "123" - reply.TrustedAccountKey = "123" - reply.PrimaryAppID = "456" -} - -var cfgFn = func(cfg *newrelic.Config) { - cfg.Enabled = false - cfg.DistributedTracer.Enabled = true - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.Attributes.Include = append(cfg.Attributes.Include, - newrelic.AttributeMessageRoutingKey, - newrelic.AttributeMessageQueueName, - newrelic.AttributeMessageExchangeType, - newrelic.AttributeMessageReplyTo, - newrelic.AttributeMessageCorrelationID, - ) -} - -func newTestWrappedClientAndServer(app newrelic.Application, wrapperOption client.Option, t *testing.T) (client.Client, server.Server) { - registry := rmemory.NewRegistry() - sel := selector.NewSelector(selector.Registry(registry)) - c := client.NewClient( - client.Selector(sel), - wrapperOption, - ) - s := server.NewServer( - server.Name(serverName), - server.Registry(registry), - server.WrapHandler(HandlerWrapper(app)), - ) - s.Handle(s.NewHandler(new(TestHandler))) - s.Handle(s.NewHandler(new(TestHandlerWithError))) - s.Handle(s.NewHandler(new(TestHandlerWithNonMicroError))) - - if err := s.Start(); nil != err { - t.Fatal(err) - } - return c, s -} - -func TestClientCallWithNoTransaction(t *testing.T) { - c, s := newTestWrappedClientAndServer(createTestApp(), client.Wrap(ClientWrapper()), t) - defer s.Stop() - testClientCallWithNoTransaction(c, t) -} - -func TestClientCallWrapperWithNoTransaction(t *testing.T) { - c, s := newTestWrappedClientAndServer(createTestApp(), client.WrapCall(CallWrapper()), t) - defer s.Stop() - testClientCallWithNoTransaction(c, t) -} - -func testClientCallWithNoTransaction(c client.Client, t *testing.T) { - - ctx := context.Background() - req := c.NewRequest(serverName, "TestHandler.Method", &TestRequest{}, client.WithContentType("application/json")) - rsp := TestResponse{} - if err := c.Call(ctx, req, &rsp); nil != err { - t.Fatal("Error calling test client:", err) - } - if rsp.RequestHeaders != missingHeaders { - t.Error("Header should not be here", rsp.RequestHeaders) - } -} - -func TestClientCallWithTransaction(t *testing.T) { - c, s := newTestWrappedClientAndServer(createTestApp(), client.Wrap(ClientWrapper()), t) - defer s.Stop() - testClientCallWithTransaction(c, t) -} - -func TestClientCallWrapperWithTransaction(t *testing.T) { - c, s := newTestWrappedClientAndServer(createTestApp(), client.WrapCall(CallWrapper()), t) - defer s.Stop() - testClientCallWithTransaction(c, t) -} - -func testClientCallWithTransaction(c client.Client, t *testing.T) { - - req := c.NewRequest(serverName, "TestHandler.Method", &TestRequest{}, client.WithContentType("application/json")) - rsp := TestResponse{} - app := createTestApp() - txn := app.StartTransaction("name", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - if err := c.Call(ctx, req, &rsp); nil != err { - t.Fatal("Error calling test client:", err) - } - if !dtHeadersFound(rsp.RequestHeaders) { - t.Error("Incorrect header:", rsp.RequestHeaders) - } - - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/name", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/name", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/testing/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/testing/Micro/TestHandler.Method", Scope: "OtherTransaction/Go/name", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/name", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "http", - "component": "Micro", - "name": "External/testing/Micro/TestHandler.Method", - "parentId": internal.MatchAnything, - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/name", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/name", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "External/testing/Micro/TestHandler.Method", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) -} - -func TestClientCallMetadata(t *testing.T) { - c, s := newTestWrappedClientAndServer(createTestApp(), client.Wrap(ClientWrapper()), t) - defer s.Stop() - testClientCallMetadata(c, t) -} - -func TestCallMetadata(t *testing.T) { - c, s := newTestWrappedClientAndServer(createTestApp(), client.WrapCall(CallWrapper()), t) - defer s.Stop() - testClientCallMetadata(c, t) -} - -func testClientCallMetadata(c client.Client, t *testing.T) { - // test that context metadata is not changed by the newrelic wrapper - req := c.NewRequest(serverName, "TestHandler.Method", &TestRequest{}, client.WithContentType("application/json")) - rsp := TestResponse{} - app := createTestApp() - txn := app.StartTransaction("name", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - md := metadata.Metadata{ - "zip": "zap", - } - ctx = metadata.NewContext(ctx, md) - if err := c.Call(ctx, req, &rsp); nil != err { - t.Fatal("Error calling test client:", err) - } - if len(md) != 1 || md["zip"] != "zap" { - t.Error("metadata changed:", md) - } -} - -func waitOrTimeout(t *testing.T, wg *sync.WaitGroup) { - ch := make(chan struct{}) - go func() { - defer close(ch) - wg.Wait() - }() - select { - case <-ch: - case <-time.After(time.Second): - t.Fatal("timeout waiting for message") - } -} - -func TestClientPublishWithNoTransaction(t *testing.T) { - c, _, b := newTestClientServerAndBroker(createTestApp(), t) - - var wg sync.WaitGroup - if err := b.Connect(); nil != err { - t.Fatal("broker connect error:", err) - } - defer b.Disconnect() - if _, err := b.Subscribe(topic, func(e broker.Event) error { - defer wg.Done() - h := e.Message().Header - if _, ok := h[newrelic.DistributedTracePayloadHeader]; ok { - t.Error("Distributed tracing headers found", h) - } - return nil - }); nil != err { - t.Fatal("Failure to subscribe to broker:", err) - } - - ctx := context.Background() - msg := c.NewMessage(topic, "hello world") - wg.Add(1) - if err := c.Publish(ctx, msg); nil != err { - t.Fatal("Error calling test client:", err) - } - waitOrTimeout(t, &wg) -} - -func TestClientPublishWithTransaction(t *testing.T) { - c, _, b := newTestClientServerAndBroker(createTestApp(), t) - - var wg sync.WaitGroup - if err := b.Connect(); nil != err { - t.Fatal("broker connect error:", err) - } - defer b.Disconnect() - if _, err := b.Subscribe(topic, func(e broker.Event) error { - defer wg.Done() - h := e.Message().Header - if _, ok := h[newrelic.DistributedTracePayloadHeader]; !ok { - t.Error("Distributed tracing headers not found", h) - } - return nil - }); nil != err { - t.Fatal("Failure to subscribe to broker:", err) - } - - app := createTestApp() - txn := app.StartTransaction("name", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - msg := c.NewMessage(topic, "hello world") - wg.Add(1) - if err := c.Publish(ctx, msg); nil != err { - t.Fatal("Error calling test client:", err) - } - waitOrTimeout(t, &wg) - - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/Micro/Topic/Produce/Named/topic", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/Micro/Topic/Produce/Named/topic", Scope: "OtherTransaction/Go/name", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/name", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/name", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/name", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "MessageBroker/Micro/Topic/Produce/Named/topic", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/name", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/name", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "MessageBroker/Micro/Topic/Produce/Named/topic", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) -} - -func TestExtractHost(t *testing.T) { - testcases := []struct { - input string - expect string - }{ - { - input: "192.168.0.10", - expect: "192.168.0.10", - }, - { - input: "192.168.0.10:1234", - expect: "192.168.0.10:1234", - }, - { - input: "unix:///path/to/file", - expect: "localhost", - }, - { - input: "nats://127.0.0.1:4222", - expect: "127.0.0.1:4222", - }, - { - input: "scheme://user:pass@host.com:5432/path?k=v#f", - expect: "host.com:5432", - }, - } - - for _, test := range testcases { - if actual := extractHost(test.input); actual != test.expect { - t.Errorf("incorrect host value extracted: actual=%s expected=%s", actual, test.expect) - } - } -} - -func TestClientStreamWrapperWithNoTransaction(t *testing.T) { - c, s := newTestWrappedClientAndServer(createTestApp(), client.Wrap(ClientWrapper()), t) - defer s.Stop() - - ctx := context.Background() - req := c.NewRequest( - serverName, - "TestHandler.StreamingMethod", - &TestRequest{}, - client.WithContentType("application/json"), - client.StreamingRequest(), - ) - stream, err := c.Stream(ctx, req) - defer stream.Close() - if nil != err { - t.Fatal("Error calling test client:", err) - } - - var resp string - err = stream.Recv(&resp) - if nil != err { - t.Fatal(err) - } - if dtHeadersFound(resp) { - t.Error("dt headers found:", resp) - } - - err = stream.Recv(&resp) - if nil == err { - t.Fatal("should have received EOF error from server") - } -} - -func TestClientStreamWrapperWithTransaction(t *testing.T) { - c, s := newTestWrappedClientAndServer(createTestApp(), client.Wrap(ClientWrapper()), t) - defer s.Stop() - - app := createTestApp() - txn := app.StartTransaction("name", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - req := c.NewRequest( - serverName, - "TestHandler.StreamingMethod", - &TestRequest{}, - client.WithContentType("application/json"), - client.StreamingRequest(), - ) - stream, err := c.Stream(ctx, req) - defer stream.Close() - if nil != err { - t.Fatal("Error calling test client:", err) - } - - var resp string - // second outgoing request to server, ensures we only create a single - // metric for the entire streaming cycle - if err := stream.Send(&resp); nil != err { - t.Fatal(err) - } - - // receive the distributed trace headers from the server - if err := stream.Recv(&resp); nil != err { - t.Fatal(err) - } - if !dtHeadersFound(resp) { - t.Error("dt headers not found:", resp) - } - - // exhaust the stream - if err := stream.Recv(&resp); nil == err { - t.Fatal("should have received EOF error from server") - } - - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/name", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/name", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/testing/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/testing/Micro/TestHandler.StreamingMethod", Scope: "OtherTransaction/Go/name", Forced: false, Data: []float64{1}}, - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/name", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "http", - "component": "Micro", - "name": "External/testing/Micro/TestHandler.StreamingMethod", - "parentId": internal.MatchAnything, - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/name", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/name", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "External/testing/Micro/TestHandler.StreamingMethod", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) -} - -func TestServerWrapperWithNoApp(t *testing.T) { - c, s := newTestWrappedClientAndServer(nil, client.Wrap(ClientWrapper()), t) - defer s.Stop() - ctx := context.Background() - req := c.NewRequest(serverName, "TestHandler.Method", &TestRequest{}, client.WithContentType("application/json")) - rsp := TestResponse{} - if err := c.Call(ctx, req, &rsp); nil != err { - t.Fatal("Error calling test client:", err) - } - if rsp.RequestHeaders != missingHeaders { - t.Error("Header should not be here", rsp.RequestHeaders) - } -} - -func TestServerWrapperWithApp(t *testing.T) { - app := createTestApp() - c, s := newTestWrappedClientAndServer(app, client.Wrap(ClientWrapper()), t) - defer s.Stop() - ctx := context.Background() - txn := app.StartTransaction("txn", nil, nil) - defer txn.End() - ctx = newrelic.NewContext(ctx, txn) - req := c.NewRequest(serverName, "TestHandler.Method", &TestRequest{}, client.WithContentType("application/json")) - rsp := TestResponse{} - if err := c.Call(ctx, req, &rsp); nil != err { - t.Fatal("Error calling test client:", err) - } - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/TestHandler.Method", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction/Go/TestHandler.Method", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/TestHandler.Method", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/Method", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/Method", Scope: "WebTransaction/Go/TestHandler.Method", Forced: false, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "WebTransaction/Go/TestHandler.Method", - "nr.entryPoint": true, - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "Custom/Method", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "WebTransaction/Go/TestHandler.Method", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "WebTransaction/Go/TestHandler.Method", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "Custom/Method", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/TestHandler.Method", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - "nr.apdexPerfZone": "S", - "parent.account": 123, - "parent.transportType": "HTTP", - "parent.app": 456, - "parentId": internal.MatchAnything, - "parent.type": "App", - "parent.transportDuration": internal.MatchAnything, - "parentSpanId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "request.method": "TestHandler.Method", - "request.uri": "micro://testing/TestHandler.Method", - "request.headers.accept": "application/json", - "request.headers.contentType": "application/json", - "request.headers.contentLength": 3, - "httpResponseCode": "200", - }, - }}) -} - -func TestServerWrapperWithAppReturnsError(t *testing.T) { - app := createTestApp() - c, s := newTestWrappedClientAndServer(app, client.Wrap(ClientWrapper()), t) - defer s.Stop() - ctx := context.Background() - req := c.NewRequest(serverName, "TestHandlerWithError.Method", &TestRequest{}, client.WithContentType("application/json")) - rsp := TestResponse{} - if err := c.Call(ctx, req, &rsp); nil == err { - t.Fatal("Expected an error but did not get one") - } - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex/Go/TestHandlerWithError.Method", Scope: "", Forced: false, Data: nil}, - {Name: "Errors/all", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/WebTransaction/Go/TestHandlerWithError.Method", Scope: "", Forced: true, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransaction/Go/TestHandlerWithError.Method", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/TestHandlerWithError.Method", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "WebTransaction/Go/TestHandlerWithError.Method", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "WebTransaction/Go/TestHandlerWithError.Method", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "WebTransaction/Go/TestHandlerWithError.Method", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{}, - }}, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/TestHandlerWithError.Method", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - "nr.apdexPerfZone": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "request.method": "TestHandlerWithError.Method", - "request.uri": "micro://testing/TestHandlerWithError.Method", - "request.headers.accept": "application/json", - "request.headers.contentType": "application/json", - "request.headers.contentLength": 3, - "httpResponseCode": 401, - }, - }}) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/TestHandlerWithError.Method", - Msg: "Unauthorized", - Klass: "401", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.message": "Unauthorized", - "error.class": "401", - "transactionName": "WebTransaction/Go/TestHandlerWithError.Method", - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "sampled": "true", - }, - }}) -} - -func TestServerWrapperWithAppReturnsNonMicroError(t *testing.T) { - app := createTestApp() - c, s := newTestWrappedClientAndServer(app, client.Wrap(ClientWrapper()), t) - defer s.Stop() - ctx := context.Background() - req := c.NewRequest("testing", "TestHandlerWithNonMicroError.Method", &TestRequest{}, client.WithContentType("application/json")) - rsp := TestResponse{} - if err := c.Call(ctx, req, &rsp); nil == err { - t.Fatal("Expected an error but did not get one") - } - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "Apdex/Go/TestHandlerWithNonMicroError.Method", Scope: "", Forced: false, Data: nil}, - {Name: "Errors/all", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/WebTransaction/Go/TestHandlerWithNonMicroError.Method", Scope: "", Forced: true, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransaction/Go/TestHandlerWithNonMicroError.Method", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/TestHandlerWithNonMicroError.Method", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/TestHandlerWithNonMicroError.Method", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - "nr.apdexPerfZone": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "request.method": "TestHandlerWithNonMicroError.Method", - "request.uri": "micro://testing/TestHandlerWithNonMicroError.Method", - "request.headers.accept": "application/json", - "request.headers.contentType": "application/json", - "request.headers.contentLength": 3, - "httpResponseCode": 500, - }, - }}) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/TestHandlerWithNonMicroError.Method", - Msg: "Internal Server Error", - Klass: "500", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.message": "Internal Server Error", - "error.class": "500", - "transactionName": "WebTransaction/Go/TestHandlerWithNonMicroError.Method", - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "sampled": "true", - }, - }}) -} - -func TestServerSubscribeNoApp(t *testing.T) { - c, s, b := newTestClientServerAndBroker(nil, t) - defer s.Stop() - - var wg sync.WaitGroup - if err := b.Connect(); nil != err { - t.Fatal("broker connect error:", err) - } - defer b.Disconnect() - err := micro.RegisterSubscriber(topic, s, func(ctx context.Context, msg *proto.HelloRequest) error { - defer wg.Done() - return nil - }) - if err != nil { - t.Fatal("error registering subscriber", err) - } - if err := s.Start(); nil != err { - t.Fatal(err) - } - - ctx := context.Background() - msg := c.NewMessage(topic, &proto.HelloRequest{Name: "test"}) - wg.Add(1) - if err := c.Publish(ctx, msg); nil != err { - t.Fatal("Error calling publish:", err) - } - waitOrTimeout(t, &wg) -} - -func TestServerSubscribe(t *testing.T) { - app := createTestApp() - c, s, _ := newTestClientServerAndBroker(app, t) - - var wg sync.WaitGroup - err := micro.RegisterSubscriber(topic, s, func(ctx context.Context, msg *proto.HelloRequest) error { - txn := newrelic.FromContext(ctx) - defer newrelic.StartSegment(txn, "segment").End() - defer wg.Done() - return nil - }) - if err != nil { - t.Fatal("error registering subscriber", err) - } - if err := s.Start(); nil != err { - t.Fatal(err) - } - - ctx := context.Background() - msg := c.NewMessage(topic, &proto.HelloRequest{Name: "test"}) - wg.Add(1) - txn := app.StartTransaction("pub", nil, nil) - ctx = newrelic.NewContext(ctx, txn) - if err := c.Publish(ctx, msg); nil != err { - t.Fatal("Error calling publish:", err) - } - defer txn.End() - waitOrTimeout(t, &wg) - s.Stop() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/Message/Micro/Topic/Named/topic", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/Message/Micro/Topic/Named/topic", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/segment", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/segment", Scope: "OtherTransaction/Go/Message/Micro/Topic/Named/topic", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/Message/Micro/Topic/Named/topic", - "nr.entryPoint": true, - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "Custom/segment", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "guid": internal.MatchAnything, - "name": "OtherTransaction/Go/Message/Micro/Topic/Named/topic", - "parent.account": 123, - "parent.app": 456, - "parent.transportDuration": internal.MatchAnything, - "parent.transportType": "HTTP", - "parent.type": "App", - "parentId": internal.MatchAnything, - "parentSpanId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - AgentAttributes: map[string]interface{}{ - "message.routingKey": "topic", - }, - UserAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/Message/Micro/Topic/Named/topic", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/Message/Micro/Topic/Named/topic", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{{ - SegmentName: "Custom/segment", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{}}, - }, - }}, - }, - }}) -} - -func TestServerSubscribeWithError(t *testing.T) { - app := createTestApp() - c, s, _ := newTestClientServerAndBroker(app, t) - - var wg sync.WaitGroup - err := micro.RegisterSubscriber(topic, s, func(ctx context.Context, msg *proto.HelloRequest) error { - defer wg.Done() - return errors.New("subscriber error") - }) - if err != nil { - t.Fatal("error registering subscriber", err) - } - if err := s.Start(); nil != err { - t.Fatal(err) - } - - ctx := context.Background() - msg := c.NewMessage(topic, &proto.HelloRequest{Name: "test"}) - wg.Add(1) - if err := c.Publish(ctx, msg); nil == err { - t.Fatal("Expected error but didn't get one") - } - waitOrTimeout(t, &wg) - s.Stop() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/Message/Micro/Topic/Named/topic", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/Message/Micro/Topic/Named/topic", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Errors/all", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Errors/OtherTransaction/Go/Message/Micro/Topic/Named/topic", Scope: "", Forced: true, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/Message/Micro/Topic/Named/topic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/Message/Micro/Topic/Named/topic", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/Message/Micro/Topic/Named/topic", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{}, - }}, - }, - }}) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/Message/Micro/Topic/Named/topic", - Msg: "subscriber error", - Klass: "*errors.errorString", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.message": "subscriber error", - "error.class": "*errors.errorString", - "transactionName": "OtherTransaction/Go/Message/Micro/Topic/Named/topic", - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "guid": internal.MatchAnything, - "sampled": "true", - }, - }}) -} - -func newTestClientServerAndBroker(app newrelic.Application, t *testing.T) (client.Client, server.Server, broker.Broker) { - b := bmemory.NewBroker() - c := client.NewClient( - client.Broker(b), - client.Wrap(ClientWrapper()), - ) - s := server.NewServer( - server.Name(serverName), - server.Broker(b), - server.WrapSubscriber(SubscriberWrapper(app)), - ) - return c, s, b -} diff --git a/_integrations/nrmongo/README.md b/_integrations/nrmongo/README.md deleted file mode 100644 index 03296bd06..000000000 --- a/_integrations/nrmongo/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrmongo [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrmongo?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrmongo) - -Package `nrmongo` instruments https://github.com/mongodb/mongo-go-driver - -```go -import "github.com/newrelic/go-agent/_integrations/nrmongo" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrmongo). diff --git a/_integrations/nrmongo/example/main.go b/_integrations/nrmongo/example/main.go deleted file mode 100644 index 860725d32..000000000 --- a/_integrations/nrmongo/example/main.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "os" - "time" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrmongo" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -func main() { - config := newrelic.NewConfig("Basic Mongo Example", os.Getenv("NEW_RELIC_LICENSE_KEY")) - config.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(config) - if nil != err { - panic(err) - } - app.WaitForConnection(10 * time.Second) - - // If you have another CommandMonitor, you can pass it to NewCommandMonitor and it will get called along - // with the NR monitor - nrMon := nrmongo.NewCommandMonitor(nil) - ctx := context.Background() - - // nrMon must be added after any other monitors are added, as previous options get overwritten. - // This example assumes Mongo is running locally on port 27017 - client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017").SetMonitor(nrMon)) - if err != nil { - panic(err) - } - defer client.Disconnect(ctx) - - txn := app.StartTransaction("Mongo txn", nil, nil) - // Make sure to add the newrelic.Transaction to the context - nrCtx := newrelic.NewContext(context.Background(), txn) - collection := client.Database("testing").Collection("numbers") - _, err = collection.InsertOne(nrCtx, bson.M{"name": "exampleName", "value": "exampleValue"}) - if err != nil { - panic(err) - } - txn.End() - app.Shutdown(10 * time.Second) - -} diff --git a/_integrations/nrmongo/nrmongo.go b/_integrations/nrmongo/nrmongo.go deleted file mode 100644 index 98277841d..000000000 --- a/_integrations/nrmongo/nrmongo.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrmongo instruments https://github.com/mongodb/mongo-go-driver -// -// Use this package to instrument your MongoDB calls without having to manually -// create DatastoreSegments. To do so, first set the monitor in the connect -// options using `SetMonitor` -// (https://godoc.org/go.mongodb.org/mongo-driver/mongo/options#ClientOptions.SetMonitor): -// -// nrMon := nrmongo.NewCommandMonitor(nil) -// client, err := mongo.Connect(ctx, options.Client().SetMonitor(nrMon)) -// -// Note that it is important that this `nrmongo` monitor is the last monitor -// set, otherwise it will be overwritten. If needing to use more than one -// `event.CommandMonitor`, pass the original monitor to the -// `nrmongo.NewCommandMonitor` function: -// -// origMon := &event.CommandMonitor{ -// Started: origStarted, -// Succeeded: origSucceeded, -// Failed: origFailed, -// } -// nrMon := nrmongo.NewCommandMonitor(origMon) -// client, err := mongo.Connect(ctx, options.Client().SetMonitor(nrMon)) -// -// Then add the current transaction to the context used in any MongoDB call: -// -// ctx = newrelic.NewContext(context.Background(), txn) -// resp, err := collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159}) -package nrmongo - -import ( - "context" - "regexp" - "sync" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "go.mongodb.org/mongo-driver/event" -) - -func init() { internal.TrackUsage("integration", "datastore", "mongo") } - -type mongoMonitor struct { - segmentMap map[int64]*newrelic.DatastoreSegment - origCommMon *event.CommandMonitor - sync.Mutex -} - -// The Mongo connection ID is constructed as: `fmt.Sprintf("%s[-%d]", addr, nextConnectionID())`, -// where addr is of the form `host:port` (or `a.sock` for unix sockets) -// See https://github.com/mongodb/mongo-go-driver/blob/b39cd78ce7021252efee2fb44aa6e492d67680ef/x/mongo/driver/topology/connection.go#L68 -// and https://github.com/mongodb/mongo-go-driver/blob/b39cd78ce7021252efee2fb44aa6e492d67680ef/x/mongo/driver/address/addr.go -var connIDPattern = regexp.MustCompile(`([^:\[]+)(?::(\d+))?\[-\d+]`) - -// NewCommandMonitor returns a new `*event.CommandMonitor` -// (https://godoc.org/go.mongodb.org/mongo-driver/event#CommandMonitor). If -// provided, the original `*event.CommandMonitor` will be called as well. The -// returned `*event.CommandMonitor` creates `newrelic.DatastoreSegment`s -// (https://godoc.org/github.com/newrelic/go-agent#DatastoreSegment) for each -// database call. -// -// // Use `SetMonitor` to register the CommandMonitor. -// client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017").SetMonitor(nrmongo.NewCommandMonitor(nil))) -// if err != nil { -// log.Fatal(err) -// } -// -// // Add transaction to the context. This step is required. -// ctx = newrelic.NewContext(ctx, txn) -// -// collection := client.Database("testing").Collection("numbers") -// resp, err := collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159}) -// if err != nil { -// log.Fatal(err) -// } -func NewCommandMonitor(original *event.CommandMonitor) *event.CommandMonitor { - m := mongoMonitor{ - segmentMap: make(map[int64]*newrelic.DatastoreSegment), - origCommMon: original, - } - return &event.CommandMonitor{ - Started: m.started, - Succeeded: m.succeeded, - Failed: m.failed, - } -} - -func (m *mongoMonitor) started(ctx context.Context, e *event.CommandStartedEvent) { - if m.origCommMon != nil && m.origCommMon.Started != nil { - m.origCommMon.Started(ctx, e) - } - txn := newrelic.FromContext(ctx) - if txn == nil { - return - } - host, port := calcHostAndPort(e.ConnectionID) - sgmt := newrelic.DatastoreSegment{ - StartTime: newrelic.StartSegmentNow(txn), - Product: newrelic.DatastoreMongoDB, - Collection: collName(e), - Operation: e.CommandName, - Host: host, - PortPathOrID: port, - DatabaseName: e.DatabaseName, - } - m.addSgmt(e, &sgmt) -} - -func collName(e *event.CommandStartedEvent) string { - coll := e.Command.Lookup(e.CommandName) - collName, _ := coll.StringValueOK() - return collName -} - -func (m *mongoMonitor) addSgmt(e *event.CommandStartedEvent, sgmt *newrelic.DatastoreSegment) { - m.Lock() - defer m.Unlock() - m.segmentMap[e.RequestID] = sgmt -} - -func (m *mongoMonitor) succeeded(ctx context.Context, e *event.CommandSucceededEvent) { - m.endSgmtIfExists(e.RequestID) - if m.origCommMon != nil && m.origCommMon.Succeeded != nil { - m.origCommMon.Succeeded(ctx, e) - } -} - -func (m *mongoMonitor) failed(ctx context.Context, e *event.CommandFailedEvent) { - m.endSgmtIfExists(e.RequestID) - if m.origCommMon != nil && m.origCommMon.Failed != nil { - m.origCommMon.Failed(ctx, e) - } -} - -func (m *mongoMonitor) endSgmtIfExists(id int64) { - m.getAndRemoveSgmt(id).End() -} - -func (m *mongoMonitor) getAndRemoveSgmt(id int64) *newrelic.DatastoreSegment { - m.Lock() - defer m.Unlock() - sgmt := m.segmentMap[id] - if sgmt != nil { - delete(m.segmentMap, id) - } - return sgmt -} - -func calcHostAndPort(connID string) (host string, port string) { - // FindStringSubmatch either returns nil or an array of the size # of submatches + 1 (in this case 3) - addressParts := connIDPattern.FindStringSubmatch(connID) - if len(addressParts) == 3 { - host = addressParts[1] - port = addressParts[2] - } - return -} diff --git a/_integrations/nrmongo/nrmongo_test.go b/_integrations/nrmongo/nrmongo_test.go deleted file mode 100644 index 9f0ecd8e5..000000000 --- a/_integrations/nrmongo/nrmongo_test.go +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrmongo - -import ( - "context" - "testing" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/event" -) - -var ( - connID = "localhost:27017[-1]" - reqID int64 = 10 - raw, _ = bson.Marshal(bson.D{primitive.E{Key: "commName", Value: "collName"}, {Key: "$db", Value: "testing"}}) - ste = &event.CommandStartedEvent{ - Command: raw, - DatabaseName: "testdb", - CommandName: "commName", - RequestID: reqID, - ConnectionID: connID, - } - finishedEvent = event.CommandFinishedEvent{ - DurationNanos: 5, - CommandName: "name", - RequestID: reqID, - ConnectionID: connID, - } - se = &event.CommandSucceededEvent{ - CommandFinishedEvent: finishedEvent, - Reply: nil, - } - fe = &event.CommandFailedEvent{ - CommandFinishedEvent: finishedEvent, - Failure: "failureCause", - } -) - -func TestOrigMonitorsAreCalled(t *testing.T) { - var started, succeeded, failed bool - origMonitor := &event.CommandMonitor{ - Started: func(ctx context.Context, e *event.CommandStartedEvent) { started = true }, - Succeeded: func(ctx context.Context, e *event.CommandSucceededEvent) { succeeded = true }, - Failed: func(ctx context.Context, e *event.CommandFailedEvent) { failed = true }, - } - ctx := context.Background() - nrMonitor := NewCommandMonitor(origMonitor) - - nrMonitor.Started(ctx, ste) - if !started { - t.Error("started not called") - } - nrMonitor.Succeeded(ctx, se) - if !succeeded { - t.Error("succeeded not called") - } - nrMonitor.Failed(ctx, fe) - if !failed { - t.Error("failed not called") - } -} - -func TestClientOptsWithNullFunctions(t *testing.T) { - origMonitor := &event.CommandMonitor{} // the monitor isn't nil, but its functions are. - ctx := context.Background() - nrMonitor := NewCommandMonitor(origMonitor) - - // Verifying no nil pointer dereferences - nrMonitor.Started(ctx, ste) - nrMonitor.Succeeded(ctx, se) - nrMonitor.Failed(ctx, fe) -} - -func TestHostAndPort(t *testing.T) { - type hostAndPort struct { - host string - port string - } - testCases := map[string]hostAndPort{ - "localhost:8080[-1]": {host: "localhost", port: "8080"}, - "something.com:987[-789]": {host: "something.com", port: "987"}, - "thisformatiswrong": {host: "", port: ""}, - "somethingunix.sock[-876]": {host: "somethingunix.sock", port: ""}, - "/var/dir/path/somethingunix.sock[-876]": {host: "/var/dir/path/somethingunix.sock", port: ""}, - } - for test, expected := range testCases { - h, p := calcHostAndPort(test) - if expected.host != h { - t.Errorf("unexpected host - expected %s, got %s", expected.host, h) - } - if expected.port != p { - t.Errorf("unexpected port - expected %s, got %s", expected.port, p) - } - } -} - -func TestMonitor(t *testing.T) { - var started, succeeded, failed bool - origMonitor := &event.CommandMonitor{ - Started: func(ctx context.Context, e *event.CommandStartedEvent) { started = true }, - Succeeded: func(ctx context.Context, e *event.CommandSucceededEvent) { succeeded = true }, - Failed: func(ctx context.Context, e *event.CommandFailedEvent) { failed = true }, - } - nrMonitor := mongoMonitor{ - segmentMap: make(map[int64]*newrelic.DatastoreSegment), - origCommMon: origMonitor, - } - app := createTestApp() - txn := app.StartTransaction("txnName", nil, nil) - ctx := newrelic.NewContext(context.Background(), txn) - nrMonitor.started(ctx, ste) - if !started { - t.Error("Original monitor not started") - } - if len(nrMonitor.segmentMap) != 1 { - t.Errorf("Wrong number of segments, expected 1 but got %d", len(nrMonitor.segmentMap)) - } - nrMonitor.succeeded(ctx, se) - if !succeeded { - t.Error("Original monitor not succeeded") - } - if len(nrMonitor.segmentMap) != 0 { - t.Errorf("Wrong number of segments, expected 0 but got %d", len(nrMonitor.segmentMap)) - } - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransactionTotalTime/Go/txnName", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/instance/MongoDB/" + internal.ThisHost + "/27017", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/operation/MongoDB/commName", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/txnName", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allOther", Scope: "", Forced: true, Data: []float64{1.0}}, - {Name: "Datastore/MongoDB/all", Scope: "", Forced: true, Data: []float64{1.0}}, - {Name: "Datastore/MongoDB/allOther", Scope: "", Forced: true, Data: []float64{1.0}}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MongoDB/collName/commName", Scope: "", Forced: false, Data: []float64{1.0}}, - {Name: "Datastore/statement/MongoDB/collName/commName", Scope: "OtherTransaction/Go/txnName", Forced: false, Data: []float64{1.0}}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/txnName", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "name": "Datastore/statement/MongoDB/collName/commName", - "sampled": true, - "category": "datastore", - "component": "MongoDB", - "span.kind": "client", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "peer.address": internal.ThisHost + ":27017", - "peer.hostname": internal.ThisHost, - "db.statement": "'commName' on 'collName' using 'MongoDB'", - "db.instance": "testdb", - "db.collection": "collName", - }, - }, - }) - - txn = app.StartTransaction("txnName", nil, nil) - ctx = newrelic.NewContext(context.Background(), txn) - nrMonitor.started(ctx, ste) - if len(nrMonitor.segmentMap) != 1 { - t.Errorf("Wrong number of segments, expected 1 but got %d", len(nrMonitor.segmentMap)) - } - nrMonitor.failed(ctx, fe) - if !failed { - t.Error("Original monitor not succeeded") - } - if len(nrMonitor.segmentMap) != 0 { - t.Errorf("Wrong number of segments, expected 0 but got %d", len(nrMonitor.segmentMap)) - } - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransactionTotalTime/Go/txnName", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/instance/MongoDB/" + internal.ThisHost + "/27017", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/operation/MongoDB/commName", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/txnName", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allOther", Scope: "", Forced: true, Data: []float64{2.0}}, - {Name: "Datastore/MongoDB/all", Scope: "", Forced: true, Data: []float64{2.0}}, - {Name: "Datastore/MongoDB/allOther", Scope: "", Forced: true, Data: []float64{2.0}}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MongoDB/collName/commName", Scope: "", Forced: false, Data: []float64{2.0}}, - {Name: "Datastore/statement/MongoDB/collName/commName", Scope: "OtherTransaction/Go/txnName", Forced: false, Data: []float64{2.0}}, - }) -} - -func TestCollName(t *testing.T) { - command := "find" - ex1, _ := bson.Marshal(bson.D{{Key: command, Value: "numbers"}, {Key: "$db", Value: "testing"}}) - ex2, _ := bson.Marshal(bson.D{{Key: "filter", Value: ""}}) - testCases := map[string]bson.Raw{ - "numbers": ex1, - "": ex2, - } - for name, raw := range testCases { - e := event.CommandStartedEvent{ - Command: raw, - CommandName: command, - } - result := collName(&e) - if result != name { - t.Errorf("Wrong collection name: %s", result) - } - } - -} - -func createTestApp() integrationsupport.ExpectApp { - return integrationsupport.NewTestApp(replyFn, cfgFn) -} - -var cfgFn = func(cfg *newrelic.Config) { - cfg.Enabled = false - cfg.DistributedTracer.Enabled = true - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 -} - -var replyFn = func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} -} diff --git a/_integrations/nrmysql/README.md b/_integrations/nrmysql/README.md deleted file mode 100644 index c1f8ed267..000000000 --- a/_integrations/nrmysql/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrmysql [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrmysql?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrmysql) - -Package `nrmysql` instruments https://github.com/go-sql-driver/mysql. - -```go -import "github.com/newrelic/go-agent/_integrations/nrmysql" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrmysql). diff --git a/_integrations/nrmysql/example/main.go b/_integrations/nrmysql/example/main.go deleted file mode 100644 index 63f6666f4..000000000 --- a/_integrations/nrmysql/example/main.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "database/sql" - "fmt" - "os" - "time" - - "github.com/newrelic/go-agent" - _ "github.com/newrelic/go-agent/_integrations/nrmysql" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - // Set up a local mysql docker container with: - // docker run -it -p 3306:3306 --net "bridge" -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql - - db, err := sql.Open("nrmysql", "root@/information_schema") - if nil != err { - panic(err) - } - - cfg := newrelic.NewConfig("MySQL App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - app.WaitForConnection(5 * time.Second) - txn := app.StartTransaction("mysqlQuery", nil, nil) - - ctx := newrelic.NewContext(context.Background(), txn) - row := db.QueryRowContext(ctx, "SELECT count(*) from tables") - var count int - row.Scan(&count) - - txn.End() - app.Shutdown(5 * time.Second) - - fmt.Println("number of tables in information_schema", count) -} diff --git a/_integrations/nrmysql/nrmysql.go b/_integrations/nrmysql/nrmysql.go deleted file mode 100644 index 78b2638b5..000000000 --- a/_integrations/nrmysql/nrmysql.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.10 - -// Package nrmysql instruments https://github.com/go-sql-driver/mysql. -// -// Use this package to instrument your MySQL calls without having to manually -// create DatastoreSegments. This is done in a two step process: -// -// 1. Use this package's driver in place of the mysql driver. -// -// If your code is using sql.Open like this: -// -// import ( -// _ "github.com/go-sql-driver/mysql" -// ) -// -// func main() { -// db, err := sql.Open("mysql", "user@unix(/path/to/socket)/dbname") -// } -// -// Then change the side-effect import to this package, and open "nrmysql" instead: -// -// import ( -// _ "github.com/newrelic/go-agent/_integrations/nrmysql" -// ) -// -// func main() { -// db, err := sql.Open("nrmysql", "user@unix(/path/to/socket)/dbname") -// } -// -// 2. Provide a context containing a newrelic.Transaction to all exec and query -// methods on sql.DB, sql.Conn, sql.Tx, and sql.Stmt. This requires using the -// context methods ExecContext, QueryContext, and QueryRowContext in place of -// Exec, Query, and QueryRow respectively. For example, instead of the -// following: -// -// row := db.QueryRow("SELECT count(*) from tables") -// -// Do this: -// -// ctx := newrelic.NewContext(context.Background(), txn) -// row := db.QueryRowContext(ctx, "SELECT count(*) from tables") -// -// A working example is shown here: -// https://github.com/newrelic/go-agent/tree/master/_integrations/nrmysql/example/main.go -package nrmysql - -import ( - "database/sql" - "net" - - "github.com/go-sql-driver/mysql" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/sqlparse" -) - -var ( - baseBuilder = newrelic.SQLDriverSegmentBuilder{ - BaseSegment: newrelic.DatastoreSegment{ - Product: newrelic.DatastoreMySQL, - }, - ParseQuery: sqlparse.ParseQuery, - ParseDSN: parseDSN, - } -) - -func init() { - sql.Register("nrmysql", newrelic.InstrumentSQLDriver(mysql.MySQLDriver{}, baseBuilder)) - internal.TrackUsage("integration", "driver", "mysql") -} - -func parseDSN(s *newrelic.DatastoreSegment, dsn string) { - cfg, err := mysql.ParseDSN(dsn) - if nil != err { - return - } - parseConfig(s, cfg) -} - -func parseConfig(s *newrelic.DatastoreSegment, cfg *mysql.Config) { - s.DatabaseName = cfg.DBName - - var host, ppoid string - switch cfg.Net { - case "unix", "unixgram", "unixpacket": - host = "localhost" - ppoid = cfg.Addr - case "cloudsql": - host = cfg.Addr - default: - var err error - host, ppoid, err = net.SplitHostPort(cfg.Addr) - if nil != err { - host = cfg.Addr - } else if host == "" { - host = "localhost" - } - } - - s.Host = host - s.PortPathOrID = ppoid -} diff --git a/_integrations/nrmysql/nrmysql_test.go b/_integrations/nrmysql/nrmysql_test.go deleted file mode 100644 index 7526a8591..000000000 --- a/_integrations/nrmysql/nrmysql_test.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrmysql - -import ( - "testing" - - "github.com/go-sql-driver/mysql" - newrelic "github.com/newrelic/go-agent" -) - -func TestParseDSN(t *testing.T) { - testcases := []struct { - dsn string - expHost string - expPortPathOrID string - expDatabaseName string - }{ - // examples from https://github.com/go-sql-driver/mysql README - { - dsn: "user@unix(/path/to/socket)/dbname", - expHost: "localhost", - expPortPathOrID: "/path/to/socket", - expDatabaseName: "dbname", - }, - { - dsn: "root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local", - expHost: "localhost", - expPortPathOrID: "/tmp/mysql.sock", - expDatabaseName: "myDatabase", - }, - { - dsn: "user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true", - expHost: "localhost", - expPortPathOrID: "5555", - expDatabaseName: "dbname", - }, - { - dsn: "user:password@/dbname?sql_mode=TRADITIONAL", - expHost: "127.0.0.1", - expPortPathOrID: "3306", - expDatabaseName: "dbname", - }, - { - dsn: "user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci", - expHost: "de:ad:be:ef::ca:fe", - expPortPathOrID: "80", - expDatabaseName: "dbname", - }, - { - dsn: "id:password@tcp(your-amazonaws-uri.com:3306)/dbname", - expHost: "your-amazonaws-uri.com", - expPortPathOrID: "3306", - expDatabaseName: "dbname", - }, - { - dsn: "user@cloudsql(project-id:instance-name)/dbname", - expHost: "project-id:instance-name", - expPortPathOrID: "", - expDatabaseName: "dbname", - }, - { - dsn: "user@cloudsql(project-id:regionname:instance-name)/dbname", - expHost: "project-id:regionname:instance-name", - expPortPathOrID: "", - expDatabaseName: "dbname", - }, - { - dsn: "user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped", - expHost: "127.0.0.1", - expPortPathOrID: "3306", - expDatabaseName: "dbname", - }, - { - dsn: "user:password@/dbname", - expHost: "127.0.0.1", - expPortPathOrID: "3306", - expDatabaseName: "dbname", - }, - { - dsn: "user:password@/", - expHost: "127.0.0.1", - expPortPathOrID: "3306", - expDatabaseName: "", - }, - { - dsn: "this is not a dsn", - expHost: "", - expPortPathOrID: "", - expDatabaseName: "", - }, - } - - for _, test := range testcases { - s := &newrelic.DatastoreSegment{} - parseDSN(s, test.dsn) - if test.expHost != s.Host { - t.Errorf(`incorrect host, expected="%s", actual="%s"`, test.expHost, s.Host) - } - if test.expPortPathOrID != s.PortPathOrID { - t.Errorf(`incorrect port path or id, expected="%s", actual="%s"`, test.expPortPathOrID, s.PortPathOrID) - } - if test.expDatabaseName != s.DatabaseName { - t.Errorf(`incorrect database name, expected="%s", actual="%s"`, test.expDatabaseName, s.DatabaseName) - } - } -} - -func TestParseConfig(t *testing.T) { - testcases := []struct { - cfgNet string - cfgAddr string - cfgDBName string - expHost string - expPortPathOrID string - expDatabaseName string - }{ - { - cfgDBName: "mydb", - expDatabaseName: "mydb", - }, - { - cfgNet: "unixgram", - cfgAddr: "/path/to/my/sock", - expHost: "localhost", - expPortPathOrID: "/path/to/my/sock", - }, - { - cfgNet: "unixpacket", - cfgAddr: "/path/to/my/sock", - expHost: "localhost", - expPortPathOrID: "/path/to/my/sock", - }, - { - cfgNet: "udp", - cfgAddr: "[fe80::1%lo0]:53", - expHost: "fe80::1%lo0", - expPortPathOrID: "53", - }, - { - cfgNet: "tcp", - cfgAddr: ":80", - expHost: "localhost", - expPortPathOrID: "80", - }, - { - cfgNet: "ip4:1", - cfgAddr: "192.0.2.1", - expHost: "192.0.2.1", - expPortPathOrID: "", - }, - { - cfgNet: "tcp6", - cfgAddr: "golang.org:http", - expHost: "golang.org", - expPortPathOrID: "http", - }, - { - cfgNet: "ip6:ipv6-icmp", - cfgAddr: "2001:db8::1", - expHost: "2001:db8::1", - expPortPathOrID: "", - }, - } - - for _, test := range testcases { - s := &newrelic.DatastoreSegment{} - cfg := &mysql.Config{ - Net: test.cfgNet, - Addr: test.cfgAddr, - DBName: test.cfgDBName, - } - parseConfig(s, cfg) - if test.expHost != s.Host { - t.Errorf(`incorrect host, expected="%s", actual="%s"`, test.expHost, s.Host) - } - if test.expPortPathOrID != s.PortPathOrID { - t.Errorf(`incorrect port path or id, expected="%s", actual="%s"`, test.expPortPathOrID, s.PortPathOrID) - } - if test.expDatabaseName != s.DatabaseName { - t.Errorf(`incorrect database name, expected="%s", actual="%s"`, test.expDatabaseName, s.DatabaseName) - } - } -} diff --git a/_integrations/nrnats/README.md b/_integrations/nrnats/README.md deleted file mode 100644 index be66253f2..000000000 --- a/_integrations/nrnats/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrnats [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrnats?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrnats) - -Package `nrnats` instruments https://github.com/nats-io/nats.go. - -```go -import "github.com/newrelic/go-agent/_integrations/nrnats" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrnats). diff --git a/_integrations/nrnats/example_test.go b/_integrations/nrnats/example_test.go deleted file mode 100644 index 5cec47716..000000000 --- a/_integrations/nrnats/example_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrnats - -import ( - "fmt" - "time" - - "github.com/nats-io/nats.go" - "github.com/nats-io/stan.go" - newrelic "github.com/newrelic/go-agent" -) - -func currentTransaction() newrelic.Transaction { return nil } - -func ExampleStartPublishSegment() { - nc, _ := nats.Connect(nats.DefaultURL) - txn := currentTransaction() - subject := "testing.subject" - - // Start the Publish segment - seg := StartPublishSegment(txn, nc, subject) - err := nc.Publish(subject, []byte("Hello World")) - if nil != err { - panic(err) - } - // Manually end the segment - seg.End() -} - -func ExampleStartPublishSegment_defer() { - nc, _ := nats.Connect(nats.DefaultURL) - txn := currentTransaction() - subject := "testing.subject" - - // Start the Publish segment and defer End till the func returns - defer StartPublishSegment(txn, nc, subject).End() - m, err := nc.Request(subject, []byte("request"), time.Second) - if nil != err { - panic(err) - } - fmt.Println("Received reply message:", string(m.Data)) -} - -var clusterID, clientID string - -// StartPublishSegment can be used with a NATS Streamming Connection as well -// (https://github.com/nats-io/stan.go). Use the `NatsConn()` method on the -// `stan.Conn` interface (https://godoc.org/github.com/nats-io/stan#Conn) to -// access the `nats.Conn` object. -func ExampleStartPublishSegment_stan() { - sc, _ := stan.Connect(clusterID, clientID) - txn := currentTransaction() - subject := "testing.subject" - - defer StartPublishSegment(txn, sc.NatsConn(), subject).End() - sc.Publish(subject, []byte("Hello World")) -} diff --git a/_integrations/nrnats/examples/README.md b/_integrations/nrnats/examples/README.md deleted file mode 100644 index dfb9d8cdf..000000000 --- a/_integrations/nrnats/examples/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Example NATS app -In this example app you can find several different ways of instrumenting NATS functions using New Relic. In order to run the app, make sure the following assumptions are correct: -* Your New Relic license key is available as an environment variable named `NEW_RELIC_LICENSE_KEY` -* A NATS server is running locally at the `nats.DefaultURL` - \ No newline at end of file diff --git a/_integrations/nrnats/examples/main.go b/_integrations/nrnats/examples/main.go deleted file mode 100644 index 24542dd09..000000000 --- a/_integrations/nrnats/examples/main.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "os" - "sync" - "time" - - "github.com/nats-io/nats.go" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrnats" -) - -var app newrelic.Application - -func doAsync(nc *nats.Conn, txn newrelic.Transaction) { - wg := sync.WaitGroup{} - subj := "async" - - // Simple Async Subscriber - // Use the nrnats.SubWrapper to wrap the nats.MsgHandler and create a - // newrelic.Transaction with each processed nats.Msg - _, err := nc.Subscribe(subj, nrnats.SubWrapper(app, func(m *nats.Msg) { - defer wg.Done() - fmt.Println("Received async message:", string(m.Data)) - })) - if nil != err { - panic(err) - } - - // Simple Publisher - wg.Add(1) - // Use nrnats.StartPublishSegment to create a - // newrelic.MessageProducerSegment for the call to nc.Publish - seg := nrnats.StartPublishSegment(txn, nc, subj) - err = nc.Publish(subj, []byte("Hello World")) - seg.End() - if nil != err { - panic(err) - } - - wg.Wait() -} - -func doQueue(nc *nats.Conn, txn newrelic.Transaction) { - wg := sync.WaitGroup{} - subj := "queue" - - // Queue Subscriber - // Use the nrnats.SubWrapper to wrap the nats.MsgHandler and create a - // newrelic.Transaction with each processed nats.Msg - _, err := nc.QueueSubscribe(subj, "myQueueName", nrnats.SubWrapper(app, func(m *nats.Msg) { - defer wg.Done() - fmt.Println("Received queue message:", string(m.Data)) - })) - if nil != err { - panic(err) - } - - wg.Add(1) - // Use nrnats.StartPublishSegment to create a - // newrelic.MessageProducerSegment for the call to nc.Publish - seg := nrnats.StartPublishSegment(txn, nc, subj) - err = nc.Publish(subj, []byte("Hello World")) - seg.End() - if nil != err { - panic(err) - } - - wg.Wait() -} - -func doSync(nc *nats.Conn, txn newrelic.Transaction) { - subj := "sync" - - // Simple Sync Subscriber - sub, err := nc.SubscribeSync(subj) - if nil != err { - panic(err) - } - // Use nrnats.StartPublishSegment to create a - // newrelic.MessageProducerSegment for the call to nc.Publish - seg := nrnats.StartPublishSegment(txn, nc, subj) - err = nc.Publish(subj, []byte("Hello World")) - seg.End() - if nil != err { - panic(err) - } - m, err := sub.NextMsg(time.Second) - if nil != err { - panic(err) - } - fmt.Println("Received sync message:", string(m.Data)) -} - -func doChan(nc *nats.Conn, txn newrelic.Transaction) { - subj := "chan" - - // Channel Subscriber - ch := make(chan *nats.Msg) - _, err := nc.ChanSubscribe(subj, ch) - if nil != err { - panic(err) - } - - // Use nrnats.StartPublishSegment to create a - // newrelic.MessageProducerSegment for the call to nc.Publish - seg := nrnats.StartPublishSegment(txn, nc, subj) - err = nc.Publish(subj, []byte("Hello World")) - seg.End() - if nil != err { - panic(err) - } - - m := <-ch - fmt.Println("Received chan message:", string(m.Data)) -} - -func doReply(nc *nats.Conn, txn newrelic.Transaction) { - subj := "reply" - - // Replies - nc.Subscribe(subj, func(m *nats.Msg) { - // Use nrnats.StartPublishSegment to create a - // newrelic.MessageProducerSegment for the call to nc.Publish - seg := nrnats.StartPublishSegment(txn, nc, m.Reply) - nc.Publish(m.Reply, []byte("Hello World")) - seg.End() - }) - - // Requests - // Use nrnats.StartPublishSegment to create a - // newrelic.MessageProducerSegment for the call to nc.Request - seg := nrnats.StartPublishSegment(txn, nc, subj) - m, err := nc.Request(subj, []byte("request"), time.Second) - seg.End() - if nil != err { - panic(err) - } - fmt.Println("Received reply message:", string(m.Data)) -} - -func doRespond(nc *nats.Conn, txn newrelic.Transaction) { - subj := "respond" - // Respond - nc.Subscribe(subj, func(m *nats.Msg) { - // Use nrnats.StartPublishSegment to create a - // newrelic.MessageProducerSegment for the call to m.Respond - seg := nrnats.StartPublishSegment(txn, nc, m.Reply) - m.Respond([]byte("Hello World")) - seg.End() - }) - - // Requests - // Use nrnats.StartPublishSegment to create a - // newrelic.MessageProducerSegment for the call to nc.Request - seg := nrnats.StartPublishSegment(txn, nc, subj) - m, err := nc.Request(subj, []byte("request"), time.Second) - seg.End() - if nil != err { - panic(err) - } - fmt.Println("Received respond message:", string(m.Data)) -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - // Initialize agent - cfg := newrelic.NewConfig("NATS App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - var err error - app, err = newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - defer app.Shutdown(10 * time.Second) - err = app.WaitForConnection(5 * time.Second) - if nil != err { - panic(err) - } - txn := app.StartTransaction("main", nil, nil) - defer txn.End() - - // Connect to a server - nc, err := nats.Connect(nats.DefaultURL) - if nil != err { - panic(err) - } - defer nc.Drain() - - doAsync(nc, txn) - doQueue(nc, txn) - doSync(nc, txn) - doChan(nc, txn) - doReply(nc, txn) - doRespond(nc, txn) -} diff --git a/_integrations/nrnats/nrnats.go b/_integrations/nrnats/nrnats.go deleted file mode 100644 index 1fae1ff8b..000000000 --- a/_integrations/nrnats/nrnats.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrnats - -import ( - "strings" - - nats "github.com/nats-io/nats.go" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -// StartPublishSegment creates and starts a `newrelic.MessageProducerSegment` -// (https://godoc.org/github.com/newrelic/go-agent#MessageProducerSegment) for NATS -// publishers. Call this function before calling any method that publishes or -// responds to a NATS message. Call `End()` -// (https://godoc.org/github.com/newrelic/go-agent#MessageProducerSegment.End) on the -// returned newrelic.MessageProducerSegment when the publish is complete. The -// `newrelic.Transaction` and `nats.Conn` parameters are required. The subject -// parameter is the subject of the publish call and is used in metric and span -// names. -func StartPublishSegment(txn newrelic.Transaction, nc *nats.Conn, subject string) *newrelic.MessageProducerSegment { - if nil == txn { - return nil - } - if nil == nc { - return nil - } - return &newrelic.MessageProducerSegment{ - StartTime: newrelic.StartSegmentNow(txn), - Library: "NATS", - DestinationType: newrelic.MessageTopic, - DestinationName: subject, - DestinationTemporary: strings.HasPrefix(subject, "_INBOX"), - } -} - -// SubWrapper can be used to wrap the function for nats.Subscribe (https://godoc.org/github.com/nats-io/go-nats#Conn.Subscribe -// or https://godoc.org/github.com/nats-io/go-nats#EncodedConn.Subscribe) -// and nats.QueueSubscribe (https://godoc.org/github.com/nats-io/go-nats#Conn.QueueSubscribe or -// https://godoc.org/github.com/nats-io/go-nats#EncodedConn.QueueSubscribe) -// If the `newrelic.Application` parameter is non-nil, it will create a `newrelic.Transaction` and end the transaction -// when the passed function is complete. -func SubWrapper(app newrelic.Application, f func(msg *nats.Msg)) func(msg *nats.Msg) { - if app == nil { - return f - } - return func(msg *nats.Msg) { - namer := internal.MessageMetricKey{ - Library: "NATS", - DestinationType: string(newrelic.MessageTopic), - DestinationName: msg.Subject, - Consumer: true, - } - txn := app.StartTransaction(namer.Name(), nil, nil) - defer txn.End() - - integrationsupport.AddAgentAttribute(txn, internal.AttributeMessageRoutingKey, msg.Sub.Subject, nil) - integrationsupport.AddAgentAttribute(txn, internal.AttributeMessageQueueName, msg.Sub.Queue, nil) - integrationsupport.AddAgentAttribute(txn, internal.AttributeMessageReplyTo, msg.Reply, nil) - - f(msg) - } -} diff --git a/_integrations/nrnats/nrnats_doc.go b/_integrations/nrnats/nrnats_doc.go deleted file mode 100644 index 71b457de5..000000000 --- a/_integrations/nrnats/nrnats_doc.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrnats instruments https://github.com/nats-io/nats.go. -// -// This package can be used to simplify instrumenting NATS publishers and subscribers. Currently due to the nature of -// the NATS framework we are limited to two integration points: `StartPublishSegment` for publishers, and `SubWrapper` -// for subscribers. -// -// NATS publishers -// -// To generate an external segment for any method that publishes or responds to a NATS message, use the -// `StartPublishSegment` method. The resulting segment will also need to be ended. Example: -// -// nc, _ := nats.Connect(nats.DefaultURL) -// txn := currentTransaction() // current newrelic.Transaction -// subject := "testing.subject" -// seg := nrnats.StartPublishSegment(txn, nc, subject) -// err := nc.Publish(subject, []byte("Hello World")) -// if nil != err { -// panic(err) -// } -// seg.End() -// -// Or: -// -// nc, _ := nats.Connect(nats.DefaultURL) -// txn := currentTransaction() // current newrelic.Transaction -// subject := "testing.subject" -// defer nrnats.StartPublishSegment(txn, nc, subject).End() -// nc.Publish(subject, []byte("Hello World")) -// -// -// NATS subscribers -// -// The `nrnats.SubWrapper` function can be used to wrap the function for `nats.Subscribe` -// (https://godoc.org/github.com/nats-io/go-nats#Conn.Subscribe or -// https://godoc.org/github.com/nats-io/go-nats#EncodedConn.Subscribe) -// and `nats.QueueSubscribe` (https://godoc.org/github.com/nats-io/go-nats#Conn.QueueSubscribe or -// https://godoc.org/github.com/nats-io/go-nats#EncodedConn.QueueSubscribe) -// If the `newrelic.Application` parameter is non-nil, it will create a `newrelic.Transaction` and end the transaction -// when the passed function is complete. Example: -// -// nc, _ := nats.Connect(nats.DefaultURL) -// app := createNRApp() // newrelic.Application -// subject := "testing.subject" -// nc.Subscribe(subject, nrnats.SubWrapper(app, myMessageHandler)) -// -// Full Publisher/Subscriber example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrnats/examples/main.go -package nrnats - -import "github.com/newrelic/go-agent/internal" - -func init() { internal.TrackUsage("integration", "framework", "nats") } diff --git a/_integrations/nrnats/nrnats_test.go b/_integrations/nrnats/nrnats_test.go deleted file mode 100644 index d7fa71c36..000000000 --- a/_integrations/nrnats/nrnats_test.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrnats - -import ( - "os" - "sync" - "testing" - "time" - - "github.com/nats-io/nats-server/test" - "github.com/nats-io/nats.go" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -func TestMain(m *testing.M) { - s := test.RunDefaultServer() - defer s.Shutdown() - os.Exit(m.Run()) -} - -func testApp() integrationsupport.ExpectApp { - return integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, cfgFn) -} - -var cfgFn = func(cfg *newrelic.Config) { - cfg.Enabled = false - cfg.DistributedTracer.Enabled = true - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.Attributes.Include = append(cfg.Attributes.Include, - newrelic.AttributeMessageRoutingKey, - newrelic.AttributeMessageQueueName, - newrelic.AttributeMessageExchangeType, - newrelic.AttributeMessageReplyTo, - newrelic.AttributeMessageCorrelationID, - ) -} - -func TestStartPublishSegmentNilTxn(t *testing.T) { - // Make sure that a nil transaction does not cause panics - nc, err := nats.Connect(nats.DefaultURL) - if nil != err { - t.Fatal(err) - } - defer nc.Close() - - StartPublishSegment(nil, nc, "mysubject").End() -} - -func TestStartPublishSegmentNilConn(t *testing.T) { - // Make sure that a nil nats.Conn does not cause panics and does not record - // metrics - app := testApp() - txn := app.StartTransaction("testing", nil, nil) - StartPublishSegment(txn, nil, "mysubject").End() - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/testing", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/testing", Scope: "", Forced: false, Data: nil}, - }) -} - -func TestStartPublishSegmentBasic(t *testing.T) { - app := testApp() - txn := app.StartTransaction("testing", nil, nil) - nc, err := nats.Connect(nats.DefaultURL) - if nil != err { - t.Fatal(err) - } - defer nc.Close() - - StartPublishSegment(txn, nc, "mysubject").End() - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/NATS/Topic/Produce/Named/mysubject", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/NATS/Topic/Produce/Named/mysubject", Scope: "OtherTransaction/Go/testing", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/testing", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/testing", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "OtherTransaction/Go/testing", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "category": "generic", - "name": "MessageBroker/NATS/Topic/Produce/Named/mysubject", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/testing", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/testing", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "MessageBroker/NATS/Topic/Produce/Named/mysubject", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }, - }) -} - -func TestSubWrapperWithNilApp(t *testing.T) { - nc, err := nats.Connect(nats.DefaultURL) - if err != nil { - t.Fatal("Error connecting to NATS server", err) - } - wg := sync.WaitGroup{} - nc.Subscribe("subject1", SubWrapper(nil, func(msg *nats.Msg) { - wg.Done() - })) - wg.Add(1) - nc.Publish("subject1", []byte("data")) - wg.Wait() -} - -func TestSubWrapper(t *testing.T) { - nc, err := nats.Connect(nats.DefaultURL) - if err != nil { - t.Fatal("Error connecting to NATS server", err) - } - wg := sync.WaitGroup{} - app := testApp() - nc.QueueSubscribe("subject2", "queue1", WgWrapper(&wg, SubWrapper(app, func(msg *nats.Msg) {}))) - wg.Add(1) - nc.Request("subject2", []byte("data"), time.Second) - wg.Wait() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/Message/NATS/Topic/Named/subject2", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/Message/NATS/Topic/Named/subject2", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/Message/NATS/Topic/Named/subject2", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - AgentAttributes: map[string]interface{}{ - "message.replyTo": internal.MatchAnything, // starts with _INBOX - "message.routingKey": "subject2", - "message.queueName": "queue1", - }, - UserAttributes: map[string]interface{}{}, - }, - }) -} - -func TestStartPublishSegmentNaming(t *testing.T) { - testCases := []struct { - subject string - metric string - }{ - {subject: "", metric: "MessageBroker/NATS/Topic/Produce/Named/Unknown"}, - {subject: "mysubject", metric: "MessageBroker/NATS/Topic/Produce/Named/mysubject"}, - {subject: "_INBOX.asldfkjsldfjskd.ldskfjls", metric: "MessageBroker/NATS/Topic/Produce/Temp"}, - } - - nc, err := nats.Connect(nats.DefaultURL) - if nil != err { - t.Fatal(err) - } - defer nc.Close() - - for _, tc := range testCases { - app := testApp() - txn := app.StartTransaction("testing", nil, nil) - StartPublishSegment(txn, nc, tc.subject).End() - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/testing", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/testing", Scope: "", Forced: false, Data: nil}, - {Name: tc.metric, Scope: "", Forced: false, Data: nil}, - {Name: tc.metric, Scope: "OtherTransaction/Go/testing", Forced: false, Data: nil}, - }) - } -} - -// Wrapper function to ensure that the NR wrapper is done recording transaction data before wg.Done() is called -func WgWrapper(wg *sync.WaitGroup, nrWrap func(msg *nats.Msg)) func(msg *nats.Msg) { - return func(msg *nats.Msg) { - nrWrap(msg) - wg.Done() - } -} diff --git a/_integrations/nrpkgerrors/README.md b/_integrations/nrpkgerrors/README.md deleted file mode 100644 index 68f3c3cf5..000000000 --- a/_integrations/nrpkgerrors/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrpkgerrors [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrpkgerrors?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrpkgerrors) - -Package `nrpkgerrors` introduces support for https://github.com/pkg/errors. - -```go -import "github.com/newrelic/go-agent/_integrations/nrpkgerrors" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrpkgerrors). diff --git a/_integrations/nrpkgerrors/example/main.go b/_integrations/nrpkgerrors/example/main.go deleted file mode 100644 index 279eb7b36..000000000 --- a/_integrations/nrpkgerrors/example/main.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "os" - "time" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrpkgerrors" - "github.com/pkg/errors" -) - -type sampleError string - -func (e sampleError) Error() string { - return string(e) -} - -func alpha() error { - return errors.WithStack(sampleError("alpha is the cause")) -} - -func beta() error { - return errors.WithStack(alpha()) -} - -func gamma() error { - return errors.Wrap(beta(), "gamma was involved") -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("pkg/errors app", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - if err := app.WaitForConnection(5 * time.Second); nil != err { - fmt.Println(err) - } - - txn := app.StartTransaction("has-error", nil, nil) - e := gamma() - txn.NoticeError(nrpkgerrors.Wrap(e)) - txn.End() - - app.Shutdown(10 * time.Second) -} diff --git a/_integrations/nrpkgerrors/example_test.go b/_integrations/nrpkgerrors/example_test.go deleted file mode 100644 index f29d00aa0..000000000 --- a/_integrations/nrpkgerrors/example_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrpkgerrors_test - -import ( - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrpkgerrors" - "github.com/pkg/errors" -) - -type rootError string - -func (e rootError) Error() string { return string(e) } - -func makeRootError() error { - return errors.WithStack(rootError("this is the original error")) -} - -func Example() { - var txn newrelic.Transaction - e := errors.Wrap(makeRootError(), "extra information") - // Wrap the error to record stack-trace and class type information from - // the error's root cause. Here, "rootError" will be recored as the - // class and top stack-trace frame will be inside makeRootError(). - // Without nrpkgerrors.Wrap, "*errors.withStack" would be recorded as - // the class and the top stack-trace frame would be site of the - // NoticeError call. - txn.NoticeError(nrpkgerrors.Wrap(e)) -} diff --git a/_integrations/nrpkgerrors/nrkpgerrors_test.go b/_integrations/nrpkgerrors/nrkpgerrors_test.go deleted file mode 100644 index d4b71faeb..000000000 --- a/_integrations/nrpkgerrors/nrkpgerrors_test.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrpkgerrors - -import ( - "runtime" - "strings" - "testing" - - newrelic "github.com/newrelic/go-agent" - "github.com/pkg/errors" -) - -func topFrameFunction(stack []uintptr) string { - var frame runtime.Frame - frames := runtime.CallersFrames(stack) - if nil != frames { - frame, _ = frames.Next() - } - return frame.Function -} - -type basicError struct{} - -func (e basicError) Error() string { return "something went wrong" } - -func alpha(e error) error { return errors.WithStack(e) } -func beta(e error) error { return errors.WithStack(e) } -func gamma(e error) error { return errors.WithStack(e) } - -func theta(e error) error { return errors.WithMessage(e, "theta") } - -func TestWrappedStackTrace(t *testing.T) { - testcases := []struct { - Error error - ExpectTopFrame string - }{ - {Error: basicError{}, ExpectTopFrame: ""}, - {Error: alpha(basicError{}), ExpectTopFrame: "alpha"}, - {Error: alpha(beta(gamma(basicError{}))), ExpectTopFrame: "gamma"}, - {Error: alpha(theta(basicError{})), ExpectTopFrame: "alpha"}, - {Error: alpha(theta(beta(basicError{}))), ExpectTopFrame: "beta"}, - {Error: alpha(theta(beta(theta(basicError{})))), ExpectTopFrame: "beta"}, - {Error: theta(basicError{}), ExpectTopFrame: ""}, - } - - for idx, tc := range testcases { - e := Wrap(tc.Error) - st := e.(newrelic.StackTracer).StackTrace() - fn := topFrameFunction(st) - if !strings.Contains(fn, tc.ExpectTopFrame) { - t.Errorf("testcase %d: expected %s got %s", - idx, tc.ExpectTopFrame, fn) - } - } -} - -type withClass struct{ class string } - -func errorWithClass(class string) error { return withClass{class: class} } - -func (e withClass) Error() string { return "something went wrong" } -func (e withClass) ErrorClass() string { return e.class } - -type classAndCause struct { - cause error - class string -} - -func wrapWithClass(e error, class string) error { return classAndCause{cause: e, class: class} } - -func (e classAndCause) Error() string { return e.cause.Error() } -func (e classAndCause) Cause() error { return e.cause } -func (e classAndCause) ErrorClass() string { return e.class } - -func TestWrappedErrorClass(t *testing.T) { - // First choice is any ErrorClass of the immediate error. - // Second choice is any ErrorClass of the error's cause. - // Final choice is the reflect type of the error's cause. - testcases := []struct { - Error error - ExpectClass string - }{ - {Error: basicError{}, ExpectClass: "nrpkgerrors.basicError"}, - {Error: errorWithClass("zap"), ExpectClass: "zap"}, - {Error: wrapWithClass(errorWithClass("zap"), "zip"), ExpectClass: "zip"}, - {Error: theta(wrapWithClass(errorWithClass("zap"), "zip")), ExpectClass: "zap"}, - {Error: alpha(basicError{}), ExpectClass: "nrpkgerrors.basicError"}, - {Error: wrapWithClass(basicError{}, "zip"), ExpectClass: "zip"}, - {Error: alpha(wrapWithClass(basicError{}, "zip")), ExpectClass: "nrpkgerrors.basicError"}, - } - - for idx, tc := range testcases { - e := Wrap(tc.Error) - class := e.(newrelic.ErrorClasser).ErrorClass() - if class != tc.ExpectClass { - t.Errorf("testcase %d: expected %s got %s", - idx, tc.ExpectClass, class) - } - } -} diff --git a/_integrations/nrpkgerrors/nrpkgerrors.go b/_integrations/nrpkgerrors/nrpkgerrors.go deleted file mode 100644 index f442d4ac9..000000000 --- a/_integrations/nrpkgerrors/nrpkgerrors.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrpkgerrors introduces support for https://github.com/pkg/errors. -// -// This package improves the class and stack-trace fields of pkg/error errors -// when they are recorded with Transaction.NoticeError. -// -package nrpkgerrors - -import ( - "fmt" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/pkg/errors" -) - -func init() { internal.TrackUsage("integration", "pkg-errors") } - -type nrpkgerror struct { - error -} - -// stackTracer is an error that also knows about its StackTrace. -// All wrapped errors from github.com/pkg/errors implement this interface. -type stackTracer interface { - StackTrace() errors.StackTrace -} - -func deepestStackTrace(err error) errors.StackTrace { - var last stackTracer - for err != nil { - if err, ok := err.(stackTracer); ok { - last = err - } - cause, ok := err.(interface { - Cause() error - }) - if !ok { - break - } - err = cause.Cause() - } - - if last == nil { - return nil - } - return last.StackTrace() -} - -func transformStackTrace(orig errors.StackTrace) []uintptr { - st := make([]uintptr, len(orig)) - for i, frame := range orig { - st[i] = uintptr(frame) - } - return st -} - -func (e nrpkgerror) StackTrace() []uintptr { - st := deepestStackTrace(e.error) - if nil == st { - return nil - } - return transformStackTrace(st) -} - -func (e nrpkgerror) ErrorClass() string { - if ec, ok := e.error.(newrelic.ErrorClasser); ok { - return ec.ErrorClass() - } - cause := errors.Cause(e.error) - if ec, ok := cause.(newrelic.ErrorClasser); ok { - return ec.ErrorClass() - } - return fmt.Sprintf("%T", cause) -} - -// Wrap wraps a pkg/errors error so that when noticed by -// newrelic.Transaction.NoticeError it gives an improved stacktrace and class -// type. -func Wrap(e error) error { - return nrpkgerror{e} -} diff --git a/_integrations/nrpq/README.md b/_integrations/nrpq/README.md deleted file mode 100644 index 1ab62055a..000000000 --- a/_integrations/nrpq/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrpq [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrpq?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrpq) - -Package `nrpq` instruments https://github.com/lib/pq. - -```go -import "github.com/newrelic/go-agent/_integrations/nrpq" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrpq). diff --git a/_integrations/nrpq/example/main.go b/_integrations/nrpq/example/main.go deleted file mode 100644 index 78da91f2f..000000000 --- a/_integrations/nrpq/example/main.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "database/sql" - "fmt" - "os" - "time" - - newrelic "github.com/newrelic/go-agent" - _ "github.com/newrelic/go-agent/_integrations/nrpq" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - // docker run --rm -e POSTGRES_PASSWORD=docker -p 5432:5432 postgres - db, err := sql.Open("nrpostgres", "host=localhost port=5432 user=postgres dbname=postgres password=docker sslmode=disable") - if err != nil { - panic(err) - } - - cfg := newrelic.NewConfig("PostgreSQL App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - app.WaitForConnection(5 * time.Second) - txn := app.StartTransaction("postgresQuery", nil, nil) - - ctx := newrelic.NewContext(context.Background(), txn) - row := db.QueryRowContext(ctx, "SELECT count(*) FROM pg_catalog.pg_tables") - var count int - row.Scan(&count) - - txn.End() - app.Shutdown(5 * time.Second) - - fmt.Println("number of entries in pg_catalog.pg_tables", count) -} diff --git a/_integrations/nrpq/example/sqlx/main.go b/_integrations/nrpq/example/sqlx/main.go deleted file mode 100644 index bf4235af1..000000000 --- a/_integrations/nrpq/example/sqlx/main.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// An application that illustrates how to instrument jmoiron/sqlx with DatastoreSegments -// -// To run this example, be sure the environment varible NEW_RELIC_LICENSE_KEY -// is set to your license key. Postgres must be running on the default port -// 5432 and have a user "foo" and a database "bar". -// -// Adding instrumentation for the SQLx package is easy. It means you can -// make database calls without having to manually create DatastoreSegments. -// Setup can be done in two steps: -// -// Set up your driver -// -// If you are using one of our currently supported database drivers (see -// https://docs.newrelic.com/docs/agents/go-agent/get-started/go-agent-compatibility-requirements#frameworks), -// follow the instructions on installing the driver. -// -// As an example, for the `lib/pq` driver, you will use the newrelic -// integration's driver in place of the postgres driver. If your code is using -// sqlx.Open with `lib/pq` like this: -// -// import ( -// "github.com/jmoiron/sqlx" -// _ "github.com/lib/pq" -// ) -// -// func main() { -// db, err := sqlx.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full") -// } -// -// Then change the side-effect import to the integration package, and open -// "nrpostgres" instead: -// -// import ( -// "github.com/jmoiron/sqlx" -// _ "github.com/newrelic/go-agent/_integrations/nrpq" -// ) -// -// func main() { -// db, err := sqlx.Open("nrpostgres", "user=pqgotest dbname=pqgotest sslmode=verify-full") -// } -// -// If you are not using one of the supported database drivers, use the -// `InstrumentSQLDriver` -// (https://godoc.org/github.com/newrelic/go-agent#InstrumentSQLDriver) API. -// See -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrmysql/nrmysql.go -// for a full example. -// -// Add context to your database calls -// -// Next, you must provide a context containing a newrelic.Transaction to all -// methods on sqlx.DB, sqlx.NamedStmt, sqlx.Stmt, and sqlx.Tx that make a -// database call. For example, instead of the following: -// -// err := db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason") -// -// Do this: -// -// ctx := newrelic.NewContext(context.Background(), txn) -// err := db.GetContext(ctx, &jason, "SELECT * FROM person WHERE first_name=$1", "Jason") -// -package main - -import ( - "context" - "fmt" - "log" - "os" - "time" - - "github.com/jmoiron/sqlx" - newrelic "github.com/newrelic/go-agent" - _ "github.com/newrelic/go-agent/_integrations/nrpq" -) - -var schema = ` -CREATE TABLE person ( - first_name text, - last_name text, - email text -)` - -// Person is a person in the database -type Person struct { - FirstName string `db:"first_name"` - LastName string `db:"last_name"` - Email string -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func createApp() newrelic.Application { - cfg := newrelic.NewConfig("SQLx", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - log.Fatalln(err) - } - if err := app.WaitForConnection(5 * time.Second); nil != err { - log.Fatalln(err) - } - return app -} - -func main() { - // Create application - app := createApp() - defer app.Shutdown(10 * time.Second) - // Start a transaction - txn := app.StartTransaction("main", nil, nil) - defer txn.End() - // Add transaction to context - ctx := newrelic.NewContext(context.Background(), txn) - - // Connect to database using the "nrpostgres" driver - db, err := sqlx.Connect("nrpostgres", "user=foo dbname=bar sslmode=disable") - if err != nil { - log.Fatalln(err) - } - - // Create database table if it does not exist already - // When the context is passed, DatastoreSegments will be created - db.ExecContext(ctx, schema) - - // Add people to the database - // When the context is passed, DatastoreSegments will be created - tx := db.MustBegin() - tx.MustExecContext(ctx, "INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net") - tx.MustExecContext(ctx, "INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "John", "Doe", "johndoeDNE@gmail.net") - tx.Commit() - - // Read from the database - // When the context is passed, DatastoreSegments will be created - people := []Person{} - db.SelectContext(ctx, &people, "SELECT * FROM person ORDER BY first_name ASC") - jason := Person{} - db.GetContext(ctx, &jason, "SELECT * FROM person WHERE first_name=$1", "Jason") -} diff --git a/_integrations/nrpq/nrpq.go b/_integrations/nrpq/nrpq.go deleted file mode 100644 index 0f490ec6a..000000000 --- a/_integrations/nrpq/nrpq.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.10 - -// Package nrpq instruments https://github.com/lib/pq. -// -// Use this package to instrument your PostgreSQL calls without having to manually -// create DatastoreSegments. This is done in a two step process: -// -// 1. Use this package's driver in place of the postgres driver. -// -// If your code is using sql.Open like this: -// -// import ( -// _ "github.com/lib/pq" -// ) -// -// func main() { -// db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full") -// } -// -// Then change the side-effect import to this package, and open "nrpostgres" instead: -// -// import ( -// _ "github.com/newrelic/go-agent/_integrations/nrpq" -// ) -// -// func main() { -// db, err := sql.Open("nrpostgres", "user=pqgotest dbname=pqgotest sslmode=verify-full") -// } -// -// If your code is using pq.NewConnector, simply use nrpq.NewConnector -// instead. -// -// 2. Provide a context containing a newrelic.Transaction to all exec and query -// methods on sql.DB, sql.Conn, and sql.Tx. This requires using the -// context methods ExecContext, QueryContext, and QueryRowContext in place of -// Exec, Query, and QueryRow respectively. For example, instead of the -// following: -// -// row := db.QueryRow("SELECT count(*) FROM pg_catalog.pg_tables") -// -// Do this: -// -// ctx := newrelic.NewContext(context.Background(), txn) -// row := db.QueryRowContext(ctx, "SELECT count(*) FROM pg_catalog.pg_tables") -// -// Unfortunately, sql.Stmt exec and query calls are not supported since pq.stmt -// does not have ExecContext and QueryContext methods (as of June 2019, see -// https://github.com/lib/pq/pull/768). -// -// A working example is shown here: -// https://github.com/newrelic/go-agent/tree/master/_integrations/nrpq/example/main.go -package nrpq - -import ( - "database/sql" - "database/sql/driver" - "os" - "path" - "regexp" - "strings" - - "github.com/lib/pq" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/sqlparse" -) - -var ( - baseBuilder = newrelic.SQLDriverSegmentBuilder{ - BaseSegment: newrelic.DatastoreSegment{ - Product: newrelic.DatastorePostgres, - }, - ParseQuery: sqlparse.ParseQuery, - ParseDSN: parseDSN(os.Getenv), - } -) - -// NewConnector can be used in place of pq.NewConnector to get an instrumented -// PostgreSQL connector. -func NewConnector(dsn string) (driver.Connector, error) { - connector, err := pq.NewConnector(dsn) - if nil != err || nil == connector { - // Return nil rather than 'connector' since a nil pointer would - // be returned as a non-nil driver.Connector. - return nil, err - } - bld := baseBuilder - bld.ParseDSN(&bld.BaseSegment, dsn) - return newrelic.InstrumentSQLConnector(connector, bld), nil -} - -func init() { - sql.Register("nrpostgres", newrelic.InstrumentSQLDriver(&pq.Driver{}, baseBuilder)) - internal.TrackUsage("integration", "driver", "postgres") -} - -var dsnSplit = regexp.MustCompile(`(\w+)\s*=\s*('[^=]*'|[^'\s]+)`) - -func getFirstHost(value string) string { - host := strings.SplitN(value, ",", 2)[0] - host = strings.Trim(host, "[]") - return host -} - -func parseDSN(getenv func(string) string) func(*newrelic.DatastoreSegment, string) { - return func(s *newrelic.DatastoreSegment, dsn string) { - if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { - var err error - dsn, err = pq.ParseURL(dsn) - if nil != err { - return - } - } - - host := getenv("PGHOST") - hostaddr := "" - ppoid := getenv("PGPORT") - dbname := getenv("PGDATABASE") - - for _, split := range dsnSplit.FindAllStringSubmatch(dsn, -1) { - if len(split) != 3 { - continue - } - key := split[1] - value := strings.Trim(split[2], `'`) - - switch key { - case "dbname": - dbname = value - case "host": - host = getFirstHost(value) - case "hostaddr": - hostaddr = getFirstHost(value) - case "port": - ppoid = strings.SplitN(value, ",", 2)[0] - } - } - - if "" != hostaddr { - host = hostaddr - } else if "" == host { - host = "localhost" - } - if "" == ppoid { - ppoid = "5432" - } - if strings.HasPrefix(host, "/") { - // this is a unix socket - ppoid = path.Join(host, ".s.PGSQL."+ppoid) - host = "localhost" - } - - s.Host = host - s.PortPathOrID = ppoid - s.DatabaseName = dbname - } -} diff --git a/_integrations/nrpq/nrpq_test.go b/_integrations/nrpq/nrpq_test.go deleted file mode 100644 index 04c8418e7..000000000 --- a/_integrations/nrpq/nrpq_test.go +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrpq - -import ( - "testing" - - newrelic "github.com/newrelic/go-agent" -) - -func TestParseDSN(t *testing.T) { - testcases := []struct { - dsn string - expHost string - expPortPathOrID string - expDatabaseName string - env map[string]string - }{ - // urls - { - dsn: "postgresql://", - expHost: "localhost", - expPortPathOrID: "5432", - expDatabaseName: "", - }, - { - dsn: "postgresql://localhost", - expHost: "localhost", - expPortPathOrID: "5432", - expDatabaseName: "", - }, - { - dsn: "postgresql://localhost:5433", - expHost: "localhost", - expPortPathOrID: "5433", - expDatabaseName: "", - }, - { - dsn: "postgresql://localhost/mydb", - expHost: "localhost", - expPortPathOrID: "5432", - expDatabaseName: "mydb", - }, - { - dsn: "postgresql://user@localhost", - expHost: "localhost", - expPortPathOrID: "5432", - expDatabaseName: "", - }, - { - dsn: "postgresql://other@localhost/otherdb?connect_timeout=10&application_name=myapp", - expHost: "localhost", - expPortPathOrID: "5432", - expDatabaseName: "otherdb", - }, - { - dsn: "postgresql:///mydb?host=myhost.com&port=5433", - expHost: "myhost.com", - expPortPathOrID: "5433", - expDatabaseName: "mydb", - }, - { - dsn: "postgresql://[2001:db8::1234]/database", - expHost: "2001:db8::1234", - expPortPathOrID: "5432", - expDatabaseName: "database", - }, - { - dsn: "postgresql://[2001:db8::1234]:7890/database", - expHost: "2001:db8::1234", - expPortPathOrID: "7890", - expDatabaseName: "database", - }, - { - dsn: "postgresql:///dbname?host=/var/lib/postgresql", - expHost: "localhost", - expPortPathOrID: "/var/lib/postgresql/.s.PGSQL.5432", - expDatabaseName: "dbname", - }, - { - dsn: "postgresql://%2Fvar%2Flib%2Fpostgresql/dbname", - expHost: "", - expPortPathOrID: "", - expDatabaseName: "", - }, - - // key,value pairs - { - dsn: "host=1.2.3.4 port=1234 dbname=mydb", - expHost: "1.2.3.4", - expPortPathOrID: "1234", - expDatabaseName: "mydb", - }, - { - dsn: "host =1.2.3.4 port= 1234 dbname = mydb", - expHost: "1.2.3.4", - expPortPathOrID: "1234", - expDatabaseName: "mydb", - }, - { - dsn: "host = 1.2.3.4 port=\t\t1234 dbname =\n\t\t\tmydb", - expHost: "1.2.3.4", - expPortPathOrID: "1234", - expDatabaseName: "mydb", - }, - { - dsn: "host ='1.2.3.4' port= '1234' dbname = 'mydb'", - expHost: "1.2.3.4", - expPortPathOrID: "1234", - expDatabaseName: "mydb", - }, - { - dsn: `host='ain\'t_single_quote' port='port\\slash' dbname='my db spaced'`, - expHost: `ain\'t_single_quote`, - expPortPathOrID: `port\\slash`, - expDatabaseName: "my db spaced", - }, - { - dsn: `host=localhost port=so=does=this`, - expHost: "localhost", - expPortPathOrID: "so=does=this", - }, - { - dsn: "host=1.2.3.4 hostaddr=5.6.7.8", - expHost: "5.6.7.8", - expPortPathOrID: "5432", - }, - { - dsn: "hostaddr=5.6.7.8 host=1.2.3.4", - expHost: "5.6.7.8", - expPortPathOrID: "5432", - }, - { - dsn: "hostaddr=1.2.3.4", - expHost: "1.2.3.4", - expPortPathOrID: "5432", - }, - { - dsn: "host=example.com,example.org port=80,443", - expHost: "example.com", - expPortPathOrID: "80", - }, - { - dsn: "hostaddr=example.com,example.org port=80,443", - expHost: "example.com", - expPortPathOrID: "80", - }, - { - dsn: "hostaddr='' host='' port=80,", - expHost: "localhost", - expPortPathOrID: "80", - }, - { - dsn: "host=/path/to/socket", - expHost: "localhost", - expPortPathOrID: "/path/to/socket/.s.PGSQL.5432", - }, - { - dsn: "port=1234 host=/path/to/socket", - expHost: "localhost", - expPortPathOrID: "/path/to/socket/.s.PGSQL.1234", - }, - { - dsn: "host=/path/to/socket port=1234", - expHost: "localhost", - expPortPathOrID: "/path/to/socket/.s.PGSQL.1234", - }, - - // env vars - { - dsn: "host=host_string port=port_string dbname=dbname_string", - expHost: "host_string", - expPortPathOrID: "port_string", - expDatabaseName: "dbname_string", - env: map[string]string{ - "PGHOST": "host_env", - "PGPORT": "port_env", - "PGDATABASE": "dbname_env", - }, - }, - { - dsn: "", - expHost: "host_env", - expPortPathOrID: "port_env", - expDatabaseName: "dbname_env", - env: map[string]string{ - "PGHOST": "host_env", - "PGPORT": "port_env", - "PGDATABASE": "dbname_env", - }, - }, - { - dsn: "host=host_string", - expHost: "host_string", - expPortPathOrID: "5432", - env: map[string]string{ - "PGHOSTADDR": "hostaddr_env", - }, - }, - { - dsn: "hostaddr=hostaddr_string", - expHost: "hostaddr_string", - expPortPathOrID: "5432", - env: map[string]string{ - "PGHOST": "host_env", - }, - }, - { - dsn: "host=host_string hostaddr=hostaddr_string", - expHost: "hostaddr_string", - expPortPathOrID: "5432", - env: map[string]string{ - "PGHOST": "host_env", - }, - }, - } - - for _, test := range testcases { - getenv := func(env string) string { - return test.env[env] - } - - s := &newrelic.DatastoreSegment{} - parseDSN(getenv)(s, test.dsn) - - if test.expHost != s.Host { - t.Errorf(`incorrect host, expected="%s", actual="%s"`, test.expHost, s.Host) - } - if test.expPortPathOrID != s.PortPathOrID { - t.Errorf(`incorrect port path or id, expected="%s", actual="%s"`, test.expPortPathOrID, s.PortPathOrID) - } - if test.expDatabaseName != s.DatabaseName { - t.Errorf(`incorrect database name, expected="%s", actual="%s"`, test.expDatabaseName, s.DatabaseName) - } - } -} - -func TestNewConnector(t *testing.T) { - connector, err := NewConnector("client_encoding=") - if err == nil { - t.Error("error expected from invalid dsn") - } - if connector != nil { - t.Error("nil connector expected from invalid dsn") - } - connector, err = NewConnector("host=localhost port=5432 user=postgres dbname=postgres password=docker sslmode=disable") - if err != nil { - t.Error("nil error expected from valid dsn", err) - } - if connector == nil { - t.Error("non-nil connector expected from valid dsn") - } -} diff --git a/_integrations/nrsqlite3/README.md b/_integrations/nrsqlite3/README.md deleted file mode 100644 index 0bde474fd..000000000 --- a/_integrations/nrsqlite3/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrsqlite3 [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrsqlite3?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrsqlite3) - -Package `nrsqlite3` instruments https://github.com/mattn/go-sqlite3. - -```go -import "github.com/newrelic/go-agent/_integrations/nrsqlite3" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrsqlite3). diff --git a/_integrations/nrsqlite3/example/main.go b/_integrations/nrsqlite3/example/main.go deleted file mode 100644 index 72d8d82d5..000000000 --- a/_integrations/nrsqlite3/example/main.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "context" - "database/sql" - "fmt" - "os" - "time" - - newrelic "github.com/newrelic/go-agent" - _ "github.com/newrelic/go-agent/_integrations/nrsqlite3" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - db, err := sql.Open("nrsqlite3", ":memory:") - if err != nil { - panic(err) - } - defer db.Close() - - db.Exec("CREATE TABLE zaps ( zap_num INTEGER )") - db.Exec("INSERT INTO zaps (zap_num) VALUES (22)") - - cfg := newrelic.NewConfig("SQLite App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - app.WaitForConnection(5 * time.Second) - txn := app.StartTransaction("sqliteQuery", nil, nil) - - ctx := newrelic.NewContext(context.Background(), txn) - row := db.QueryRowContext(ctx, "SELECT count(*) from zaps") - var count int - row.Scan(&count) - - txn.End() - app.Shutdown(5 * time.Second) - - fmt.Println("number of entries in table", count) -} diff --git a/_integrations/nrsqlite3/nrsqlite3.go b/_integrations/nrsqlite3/nrsqlite3.go deleted file mode 100644 index 575ae4e22..000000000 --- a/_integrations/nrsqlite3/nrsqlite3.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.10 - -// Package nrsqlite3 instruments https://github.com/mattn/go-sqlite3. -// -// Use this package to instrument your SQLite calls without having to manually -// create DatastoreSegments. This is done in a two step process: -// -// 1. Use this package's driver in place of the sqlite3 driver. -// -// If your code is using sql.Open like this: -// -// import ( -// _ "github.com/mattn/go-sqlite3" -// ) -// -// func main() { -// db, err := sql.Open("sqlite3", "./foo.db") -// } -// -// Then change the side-effect import to this package, and open "nrsqlite3" instead: -// -// import ( -// _ "github.com/newrelic/go-agent/_integrations/nrsqlite3" -// ) -// -// func main() { -// db, err := sql.Open("nrsqlite3", "./foo.db") -// } -// -// If you are registering a custom sqlite3 driver with special behavior then -// you must wrap your driver instance using nrsqlite3.InstrumentSQLDriver. For -// example, if your code looks like this: -// -// func main() { -// sql.Register("sqlite3_with_extensions", &sqlite3.SQLiteDriver{ -// Extensions: []string{ -// "sqlite3_mod_regexp", -// }, -// }) -// db, err := sql.Open("sqlite3_with_extensions", ":memory:") -// } -// -// Then instrument the driver like this: -// -// func main() { -// sql.Register("sqlite3_with_extensions", nrsqlite3.InstrumentSQLDriver(&sqlite3.SQLiteDriver{ -// Extensions: []string{ -// "sqlite3_mod_regexp", -// }, -// })) -// db, err := sql.Open("sqlite3_with_extensions", ":memory:") -// } -// -// 2. Provide a context containing a newrelic.Transaction to all exec and query -// methods on sql.DB, sql.Conn, sql.Tx, and sql.Stmt. This requires using the -// context methods ExecContext, QueryContext, and QueryRowContext in place of -// Exec, Query, and QueryRow respectively. For example, instead of the -// following: -// -// row := db.QueryRow("SELECT count(*) from tables") -// -// Do this: -// -// ctx := newrelic.NewContext(context.Background(), txn) -// row := db.QueryRowContext(ctx, "SELECT count(*) from tables") -// -// A working example is shown here: -// https://github.com/newrelic/go-agent/tree/master/_integrations/nrsqlite3/example/main.go -package nrsqlite3 - -import ( - "database/sql" - "database/sql/driver" - "path/filepath" - "strings" - - sqlite3 "github.com/mattn/go-sqlite3" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/sqlparse" -) - -var ( - baseBuilder = newrelic.SQLDriverSegmentBuilder{ - BaseSegment: newrelic.DatastoreSegment{ - Product: newrelic.DatastoreSQLite, - }, - ParseQuery: sqlparse.ParseQuery, - ParseDSN: parseDSN, - } -) - -func init() { - sql.Register("nrsqlite3", InstrumentSQLDriver(&sqlite3.SQLiteDriver{})) - internal.TrackUsage("integration", "driver", "sqlite3") -} - -// InstrumentSQLDriver wraps an sqlite3.SQLiteDriver to add instrumentation. -// For example, if you are registering a custom SQLiteDriver like this: -// -// sql.Register("sqlite3_with_extensions", -// &sqlite3.SQLiteDriver{ -// Extensions: []string{ -// "sqlite3_mod_regexp", -// }, -// }) -// -// Then add instrumentation like this: -// -// sql.Register("sqlite3_with_extensions", -// nrsqlite3.InstrumentSQLDriver(&sqlite3.SQLiteDriver{ -// Extensions: []string{ -// "sqlite3_mod_regexp", -// }, -// })) -// -func InstrumentSQLDriver(d *sqlite3.SQLiteDriver) driver.Driver { - return newrelic.InstrumentSQLDriver(d, baseBuilder) -} - -func getPortPathOrID(dsn string) (ppoid string) { - ppoid = strings.Split(dsn, "?")[0] - ppoid = strings.TrimPrefix(ppoid, "file:") - - if ":memory:" != ppoid && "" != ppoid { - if abs, err := filepath.Abs(ppoid); nil == err { - ppoid = abs - } - } - - return -} - -// ParseDSN accepts a DSN string and sets the Host, PortPathOrID, and -// DatabaseName fields on a newrelic.DatastoreSegment. -func parseDSN(s *newrelic.DatastoreSegment, dsn string) { - // See https://godoc.org/github.com/mattn/go-sqlite3#SQLiteDriver.Open - s.Host = "localhost" - s.PortPathOrID = getPortPathOrID(dsn) - s.DatabaseName = "" -} diff --git a/_integrations/nrsqlite3/nrsqlite3_test.go b/_integrations/nrsqlite3/nrsqlite3_test.go deleted file mode 100644 index 6b1b00fd8..000000000 --- a/_integrations/nrsqlite3/nrsqlite3_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrsqlite3 - -import ( - "path/filepath" - "runtime" - "testing" -) - -func TestGetPortPathOrID(t *testing.T) { - _, here, _, _ := runtime.Caller(0) - currentDir := filepath.Dir(here) - - testcases := []struct { - dsn string - expected string - }{ - {":memory:", ":memory:"}, - {"test.db", filepath.Join(currentDir, "test.db")}, - {"file:/test.db?cache=shared&mode=memory", "/test.db"}, - {"file::memory:", ":memory:"}, - {"", ""}, - } - - for _, test := range testcases { - if actual := getPortPathOrID(test.dsn); actual != test.expected { - t.Errorf(`incorrect port path or id: dsn="%s", actual="%s"`, test.dsn, actual) - } - } -} diff --git a/_integrations/nrstan/README.md b/_integrations/nrstan/README.md deleted file mode 100644 index 5886b8d43..000000000 --- a/_integrations/nrstan/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrstan [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrstan?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrstan) - -Package `nrstan` instruments https://github.com/nats-io/stan.go. - -```go -import "github.com/newrelic/go-agent/_integrations/nrstan" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrstan). diff --git a/_integrations/nrstan/examples/README.md b/_integrations/nrstan/examples/README.md deleted file mode 100644 index 186846324..000000000 --- a/_integrations/nrstan/examples/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Example STAN app -In this example app you can find several different ways of instrumenting NATS Streaming functions using New Relic. In order to run the app, make sure the following assumptions are correct: -* Your New Relic license key is available as an environment variable named `NEW_RELIC_LICENSE_KEY` -* A NATS Streaming Server is running with the cluster id `test-cluster` \ No newline at end of file diff --git a/_integrations/nrstan/examples/main.go b/_integrations/nrstan/examples/main.go deleted file mode 100644 index 5949571d4..000000000 --- a/_integrations/nrstan/examples/main.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "os" - "sync" - "time" - - "github.com/nats-io/stan.go" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/_integrations/nrnats" - "github.com/newrelic/go-agent/_integrations/nrstan" -) - -var app newrelic.Application - -func doAsync(sc stan.Conn, txn newrelic.Transaction) { - wg := sync.WaitGroup{} - subj := "async" - - // Simple Async Subscriber - // Use the nrstan.StreamingSubWrapper to wrap the stan.MsgHandler and - // create a newrelic.Transaction with each processed stan.Msg - _, err := sc.Subscribe(subj, nrstan.StreamingSubWrapper(app, func(m *stan.Msg) { - defer wg.Done() - fmt.Println("Received async message:", string(m.Data)) - })) - if nil != err { - panic(err) - } - - // Simple Publisher - wg.Add(1) - // Use nrnats.StartPublishSegment to create a newrelic.ExternalSegment for - // the call to sc.Publish - seg := nrnats.StartPublishSegment(txn, sc.NatsConn(), subj) - err = sc.Publish(subj, []byte("Hello World")) - seg.End() - if nil != err { - panic(err) - } - - wg.Wait() -} - -func doQueue(sc stan.Conn, txn newrelic.Transaction) { - wg := sync.WaitGroup{} - subj := "queue" - - // Queue Subscriber - // Use the nrstan.StreamingSubWrapper to wrap the stan.MsgHandler and - // create a newrelic.Transaction with each processed stan.Msg - _, err := sc.QueueSubscribe(subj, "myqueue", nrstan.StreamingSubWrapper(app, func(m *stan.Msg) { - defer wg.Done() - fmt.Println("Received queue message:", string(m.Data)) - })) - if nil != err { - panic(err) - } - - wg.Add(1) - // Use nrnats.StartPublishSegment to create a newrelic.ExternalSegment for - // the call to sc.Publish - seg := nrnats.StartPublishSegment(txn, sc.NatsConn(), subj) - err = sc.Publish(subj, []byte("Hello World")) - seg.End() - if nil != err { - panic(err) - } - - wg.Wait() -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - // Initialize agent - cfg := newrelic.NewConfig("STAN App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - var err error - app, err = newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - defer app.Shutdown(10 * time.Second) - err = app.WaitForConnection(5 * time.Second) - if nil != err { - panic(err) - } - txn := app.StartTransaction("main", nil, nil) - defer txn.End() - - // Connect to a server - sc, err := stan.Connect("test-cluster", "clientid") - if nil != err { - panic(err) - } - defer sc.Close() - - doAsync(sc, txn) - doQueue(sc, txn) -} diff --git a/_integrations/nrstan/nrstan.go b/_integrations/nrstan/nrstan.go deleted file mode 100644 index 17e1fc2ca..000000000 --- a/_integrations/nrstan/nrstan.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrstan - -import ( - stan "github.com/nats-io/stan.go" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -// StreamingSubWrapper can be used to wrap the function for STREAMING stan.Subscribe and stan.QueueSubscribe -// (https://godoc.org/github.com/nats-io/stan.go#Conn) -// If the `newrelic.Application` parameter is non-nil, it will create a `newrelic.Transaction` and end the transaction -// when the passed function is complete. -func StreamingSubWrapper(app newrelic.Application, f func(msg *stan.Msg)) func(msg *stan.Msg) { - if app == nil { - return f - } - return func(msg *stan.Msg) { - namer := internal.MessageMetricKey{ - Library: "STAN", - DestinationType: string(newrelic.MessageTopic), - DestinationName: msg.MsgProto.Subject, - Consumer: true, - } - txn := app.StartTransaction(namer.Name(), nil, nil) - defer txn.End() - - integrationsupport.AddAgentAttribute(txn, internal.AttributeMessageRoutingKey, msg.MsgProto.Subject, nil) - integrationsupport.AddAgentAttribute(txn, internal.AttributeMessageReplyTo, msg.MsgProto.Reply, nil) - - f(msg) - } -} diff --git a/_integrations/nrstan/nrstan_doc.go b/_integrations/nrstan/nrstan_doc.go deleted file mode 100644 index c892bd8ab..000000000 --- a/_integrations/nrstan/nrstan_doc.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrstan instruments https://github.com/nats-io/stan.go. -// -// This package can be used to simplify instrumenting NATS Streaming subscribers. Currently due to the nature of -// the NATS Streaming framework we are limited to two integration points: `StartPublishSegment` for publishers, and -// `SubWrapper` for subscribers. -// -// -// NATS Streaming subscribers -// -// `nrstan.StreamingSubWrapper` can be used to wrap the function for STREAMING stan.Subscribe and stan.QueueSubscribe -// (https://godoc.org/github.com/nats-io/stan.go#Conn) If the `newrelic.Application` parameter is non-nil, it will -// create a `newrelic.Transaction` and end the transaction when the passed function is complete. Example: -// -// sc, err := stan.Connect(clusterName, clientName) -// if err != nil { -// t.Fatal("Couldn't connect to server", err) -// } -// defer sc.Close() -// app := createTestApp(t) // newrelic.Application -// sc.Subscribe(subject, StreamingSubWrapper(app, myMessageHandler) -// -// -// NATS Streaming publishers -// -// You can use `nrnats.StartPublishSegment` from the `nrnats` package -// (https://godoc.org/github.com/newrelic/go-agent/_integrations/nrnats/#StartPublishSegment) -// to start an external segment when doing a streaming publish, which must be ended after publishing is complete. -// Example: -// -// sc, err := stan.Connect(clusterName, clientName) -// if err != nil { -// t.Fatal("Couldn't connect to server", err) -// } -// txn := currentTransaction() // current newrelic.Transaction -// seg := nrnats.StartPublishSegment(txn, sc.NatsConn(), subj) -// sc.Publish(subj, []byte("Hello World")) -// seg.End() -// -// Full Publisher/Subscriber example: -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrstan/examples/main.go -package nrstan - -import "github.com/newrelic/go-agent/internal" - -func init() { internal.TrackUsage("integration", "framework", "stan") } diff --git a/_integrations/nrstan/nrstan_test.go b/_integrations/nrstan/nrstan_test.go deleted file mode 100644 index 6070b0e75..000000000 --- a/_integrations/nrstan/nrstan_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrstan - -import ( - "os" - "sync" - "testing" - - "github.com/nats-io/nats-streaming-server/server" - "github.com/nats-io/stan.go" - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/integrationsupport" -) - -const ( - clusterName = "my_test_cluster" - clientName = "me" -) - -func TestMain(m *testing.M) { - s, err := server.RunServer(clusterName) - if err != nil { - panic(err) - } - defer s.Shutdown() - os.Exit(m.Run()) -} - -func createTestApp() integrationsupport.ExpectApp { - return integrationsupport.NewTestApp(integrationsupport.SampleEverythingReplyFn, cfgFn) -} - -var cfgFn = func(cfg *newrelic.Config) { - cfg.Enabled = false - cfg.DistributedTracer.Enabled = true - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.Attributes.Include = append(cfg.Attributes.Include, - newrelic.AttributeMessageRoutingKey, - newrelic.AttributeMessageQueueName, - newrelic.AttributeMessageExchangeType, - newrelic.AttributeMessageReplyTo, - newrelic.AttributeMessageCorrelationID, - ) -} - -func TestSubWrapperWithNilApp(t *testing.T) { - subject := "sample.subject1" - sc, err := stan.Connect(clusterName, clientName) - if err != nil { - t.Fatal("Couldn't connect to server", err) - } - defer sc.Close() - - wg := sync.WaitGroup{} - sc.Subscribe(subject, StreamingSubWrapper(nil, func(msg *stan.Msg) { - defer wg.Done() - })) - wg.Add(1) - sc.Publish(subject, []byte("data")) - wg.Wait() -} - -func TestSubWrapper(t *testing.T) { - subject := "sample.subject2" - sc, err := stan.Connect(clusterName, clientName) - if err != nil { - t.Fatal("Couldn't connect to server", err) - } - defer sc.Close() - - wg := sync.WaitGroup{} - app := createTestApp() - sc.Subscribe(subject, WgWrapper(&wg, StreamingSubWrapper(app, func(msg *stan.Msg) {}))) - - wg.Add(1) - sc.Publish(subject, []byte("data")) - wg.Wait() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransaction/Go/Message/STAN/Topic/Named/sample.subject2", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/Message/STAN/Topic/Named/sample.subject2", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/Message/STAN/Topic/Named/sample.subject2", - "guid": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - AgentAttributes: map[string]interface{}{ - "message.routingKey": "sample.subject2", - }, - UserAttributes: map[string]interface{}{}, - }, - }) -} - -// Wrapper function to ensure that the NR wrapper is done recording transaction data before wg.Done() is called -func WgWrapper(wg *sync.WaitGroup, nrWrap func(msg *stan.Msg)) func(msg *stan.Msg) { - return func(msg *stan.Msg) { - nrWrap(msg) - wg.Done() - } -} diff --git a/_integrations/nrzap/README.md b/_integrations/nrzap/README.md deleted file mode 100644 index 0278aafce..000000000 --- a/_integrations/nrzap/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# _integrations/nrzap [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrzap?status.svg)](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrzap) - -Package `nrzap` supports https://github.com/uber-go/zap. - -```go -import "github.com/newrelic/go-agent/_integrations/nrzap" -``` - -For more information, see -[godocs](https://godoc.org/github.com/newrelic/go-agent/_integrations/nrzap). diff --git a/_integrations/nrzap/example_test.go b/_integrations/nrzap/example_test.go deleted file mode 100644 index 42003b0d5..000000000 --- a/_integrations/nrzap/example_test.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package nrzap - -import ( - newrelic "github.com/newrelic/go-agent" - "go.uber.org/zap" -) - -func Example() { - cfg := newrelic.NewConfig("Example App", "__YOUR_NEWRELIC_LICENSE_KEY__") - - // Create a new zap logger: - z, _ := zap.NewProduction() - - // Use nrzap to register the logger with the agent: - cfg.Logger = Transform(z.Named("newrelic")) - - newrelic.NewApplication(cfg) -} diff --git a/_integrations/nrzap/nrzap.go b/_integrations/nrzap/nrzap.go deleted file mode 100644 index d18db6f11..000000000 --- a/_integrations/nrzap/nrzap.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package nrzap supports https://github.com/uber-go/zap -// -// Wrap your zap Logger using nrzap.Transform to send agent log messages to zap. -package nrzap - -import ( - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" - "go.uber.org/zap" -) - -func init() { internal.TrackUsage("integration", "logging", "zap") } - -type shim struct{ logger *zap.Logger } - -func transformAttributes(atts map[string]interface{}) []zap.Field { - fs := make([]zap.Field, 0, len(atts)) - for key, val := range atts { - fs = append(fs, zap.Any(key, val)) - } - return fs -} - -func (s *shim) Error(msg string, c map[string]interface{}) { - s.logger.Error(msg, transformAttributes(c)...) -} -func (s *shim) Warn(msg string, c map[string]interface{}) { - s.logger.Warn(msg, transformAttributes(c)...) -} -func (s *shim) Info(msg string, c map[string]interface{}) { - s.logger.Info(msg, transformAttributes(c)...) -} -func (s *shim) Debug(msg string, c map[string]interface{}) { - s.logger.Debug(msg, transformAttributes(c)...) -} -func (s *shim) DebugEnabled() bool { - ce := s.logger.Check(zap.DebugLevel, "debugging") - return ce != nil -} - -// Transform turns a *zap.Logger into a newrelic.Logger. -func Transform(l *zap.Logger) newrelic.Logger { return &shim{logger: l} } diff --git a/app_run.go b/app_run.go deleted file mode 100644 index ae0fb00dd..000000000 --- a/app_run.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/json" - "strings" - "time" - - "github.com/newrelic/go-agent/internal" -) - -// appRun contains information regarding a single connection session with the -// collector. It is immutable after creation at application connect. -type appRun struct { - Reply *internal.ConnectReply - - // AttributeConfig is calculated on every connect since it depends on - // the security policies. - AttributeConfig *internal.AttributeConfig - Config Config - - // firstAppName is the value of Config.AppName up to the first semicolon. - firstAppName string -} - -func newAppRun(config Config, reply *internal.ConnectReply) *appRun { - convertConfig := func(c AttributeDestinationConfig) internal.AttributeDestinationConfig { - return internal.AttributeDestinationConfig{ - Enabled: c.Enabled, - Include: c.Include, - Exclude: c.Exclude, - } - } - run := &appRun{ - Reply: reply, - AttributeConfig: internal.CreateAttributeConfig(internal.AttributeConfigInput{ - Attributes: convertConfig(config.Attributes), - ErrorCollector: convertConfig(config.ErrorCollector.Attributes), - TransactionEvents: convertConfig(config.TransactionEvents.Attributes), - TransactionTracer: convertConfig(config.TransactionTracer.Attributes), - BrowserMonitoring: convertConfig(config.BrowserMonitoring.Attributes), - SpanEvents: convertConfig(config.SpanEvents.Attributes), - TraceSegments: convertConfig(config.TransactionTracer.Segments.Attributes), - }, reply.SecurityPolicies.AttributesInclude.Enabled()), - Config: config, - } - - // Overwrite local settings with any server-side-config settings - // present. NOTE! This requires that the Config provided to this - // function is a value and not a pointer: We do not want to change the - // input Config with values particular to this connection. - - if v := run.Reply.ServerSideConfig.TransactionTracerEnabled; nil != v { - run.Config.TransactionTracer.Enabled = *v - } - if v := run.Reply.ServerSideConfig.ErrorCollectorEnabled; nil != v { - run.Config.ErrorCollector.Enabled = *v - } - if v := run.Reply.ServerSideConfig.CrossApplicationTracerEnabled; nil != v { - run.Config.CrossApplicationTracer.Enabled = *v - } - if v := run.Reply.ServerSideConfig.TransactionTracerThreshold; nil != v { - switch val := v.(type) { - case float64: - run.Config.TransactionTracer.Threshold.IsApdexFailing = false - run.Config.TransactionTracer.Threshold.Duration = internal.FloatSecondsToDuration(val) - case string: - if val == "apdex_f" { - run.Config.TransactionTracer.Threshold.IsApdexFailing = true - } - } - } - if v := run.Reply.ServerSideConfig.TransactionTracerStackTraceThreshold; nil != v { - run.Config.TransactionTracer.StackTraceThreshold = internal.FloatSecondsToDuration(*v) - } - if v := run.Reply.ServerSideConfig.ErrorCollectorIgnoreStatusCodes; nil != v { - run.Config.ErrorCollector.IgnoreStatusCodes = v - } - - if !run.Reply.CollectErrorEvents { - run.Config.ErrorCollector.CaptureEvents = false - } - if !run.Reply.CollectAnalyticsEvents { - run.Config.TransactionEvents.Enabled = false - } - if !run.Reply.CollectTraces { - run.Config.TransactionTracer.Enabled = false - run.Config.DatastoreTracer.SlowQuery.Enabled = false - } - if !run.Reply.CollectSpanEvents { - run.Config.SpanEvents.Enabled = false - } - - // Distributed tracing takes priority over cross-app-tracing per: - // https://source.datanerd.us/agents/agent-specs/blob/master/Distributed-Tracing.md#distributed-trace-payload - if run.Config.DistributedTracer.Enabled { - run.Config.CrossApplicationTracer.Enabled = false - } - - // Cache the first application name set on the config - run.firstAppName = strings.SplitN(config.AppName, ";", 2)[0] - - if "" != run.Reply.RunID { - js, _ := json.Marshal(settings(run.Config)) - run.Config.Logger.Debug("final configuration", map[string]interface{}{ - "config": internal.JSONString(js), - }) - } - - return run -} - -const ( - // https://source.datanerd.us/agents/agent-specs/blob/master/Lambda.md#distributed-tracing - serverlessDefaultPrimaryAppID = "Unknown" -) - -const ( - // https://source.datanerd.us/agents/agent-specs/blob/master/Lambda.md#adaptive-sampling - serverlessSamplerPeriod = 60 * time.Second - serverlessSamplerTarget = 10 -) - -func newServerlessConnectReply(config Config) *internal.ConnectReply { - reply := internal.ConnectReplyDefaults() - - reply.ApdexThresholdSeconds = config.ServerlessMode.ApdexThreshold.Seconds() - - reply.AccountID = config.ServerlessMode.AccountID - reply.TrustedAccountKey = config.ServerlessMode.TrustedAccountKey - reply.PrimaryAppID = config.ServerlessMode.PrimaryAppID - - if "" == reply.TrustedAccountKey { - // The trust key does not need to be provided by customers whose - // account ID is the same as the trust key. - reply.TrustedAccountKey = reply.AccountID - } - - if "" == reply.PrimaryAppID { - reply.PrimaryAppID = serverlessDefaultPrimaryAppID - } - - reply.AdaptiveSampler = internal.NewAdaptiveSampler(serverlessSamplerPeriod, - serverlessSamplerTarget, time.Now()) - - return reply -} - -func (run *appRun) responseCodeIsError(code int) bool { - // Response codes below 100 are allowed to be errors to support gRPC. - if code < 400 && code >= 100 { - return false - } - for _, ignoreCode := range run.Config.ErrorCollector.IgnoreStatusCodes { - if code == ignoreCode { - return false - } - } - return true -} - -func (run *appRun) txnTraceThreshold(apdexThreshold time.Duration) time.Duration { - if run.Config.TransactionTracer.Threshold.IsApdexFailing { - return internal.ApdexFailingThreshold(apdexThreshold) - } - return run.Config.TransactionTracer.Threshold.Duration -} - -func (run *appRun) ptrTxnEvents() *uint { return run.Reply.EventData.Limits.TxnEvents } -func (run *appRun) ptrCustomEvents() *uint { return run.Reply.EventData.Limits.CustomEvents } -func (run *appRun) ptrErrorEvents() *uint { return run.Reply.EventData.Limits.ErrorEvents } -func (run *appRun) ptrSpanEvents() *uint { return run.Reply.EventData.Limits.SpanEvents } - -func (run *appRun) MaxTxnEvents() int { return run.limit(run.Config.MaxTxnEvents(), run.ptrTxnEvents) } -func (run *appRun) MaxCustomEvents() int { - return run.limit(internal.MaxCustomEvents, run.ptrCustomEvents) -} -func (run *appRun) MaxErrorEvents() int { - return run.limit(internal.MaxErrorEvents, run.ptrErrorEvents) -} -func (run *appRun) MaxSpanEvents() int { return run.limit(internal.MaxSpanEvents, run.ptrSpanEvents) } - -func (run *appRun) limit(dflt int, field func() *uint) int { - if nil != field() { - return int(*field()) - } - return dflt -} - -func (run *appRun) ReportPeriods() map[internal.HarvestTypes]time.Duration { - fixed := internal.HarvestMetricsTraces - configurable := internal.HarvestTypes(0) - - for tp, fn := range map[internal.HarvestTypes]func() *uint{ - internal.HarvestTxnEvents: run.ptrTxnEvents, - internal.HarvestCustomEvents: run.ptrCustomEvents, - internal.HarvestErrorEvents: run.ptrErrorEvents, - internal.HarvestSpanEvents: run.ptrSpanEvents, - } { - if nil != run && fn() != nil { - configurable |= tp - } else { - fixed |= tp - } - } - return map[internal.HarvestTypes]time.Duration{ - configurable: run.Reply.ConfigurablePeriod(), - fixed: internal.FixedHarvestPeriod, - } -} diff --git a/app_run_test.go b/app_run_test.go deleted file mode 100644 index 986bf2303..000000000 --- a/app_run_test.go +++ /dev/null @@ -1,388 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/json" - "fmt" - "reflect" - "testing" - "time" - - "github.com/newrelic/go-agent/internal" -) - -func TestResponseCodeIsError(t *testing.T) { - cfg := NewConfig("my app", "0123456789012345678901234567890123456789") - cfg.ErrorCollector.IgnoreStatusCodes = append(cfg.ErrorCollector.IgnoreStatusCodes, 504) - run := newAppRun(cfg, internal.ConnectReplyDefaults()) - - for _, tc := range []struct { - Code int - IsError bool - }{ - {Code: 0, IsError: false}, // gRPC - {Code: 1, IsError: true}, // gRPC - {Code: 5, IsError: false}, // gRPC - {Code: 6, IsError: true}, // gRPC - {Code: 99, IsError: true}, - {Code: 100, IsError: false}, - {Code: 199, IsError: false}, - {Code: 200, IsError: false}, - {Code: 300, IsError: false}, - {Code: 399, IsError: false}, - {Code: 400, IsError: true}, - {Code: 404, IsError: false}, - {Code: 503, IsError: true}, - {Code: 504, IsError: false}, - } { - if is := run.responseCodeIsError(tc.Code); is != tc.IsError { - t.Errorf("responseCodeIsError for %d, wanted=%v got=%v", - tc.Code, tc.IsError, is) - } - } - -} - -func TestCrossAppTracingEnabled(t *testing.T) { - // CAT should be enabled by default. - cfg := NewConfig("my app", "0123456789012345678901234567890123456789") - run := newAppRun(cfg, internal.ConnectReplyDefaults()) - if enabled := run.Config.CrossApplicationTracer.Enabled; !enabled { - t.Error(enabled) - } - - // DT gets priority over CAT. - cfg = NewConfig("my app", "0123456789012345678901234567890123456789") - cfg.DistributedTracer.Enabled = true - cfg.CrossApplicationTracer.Enabled = true - run = newAppRun(cfg, internal.ConnectReplyDefaults()) - if enabled := run.Config.CrossApplicationTracer.Enabled; enabled { - t.Error(enabled) - } - - cfg = NewConfig("my app", "0123456789012345678901234567890123456789") - cfg.DistributedTracer.Enabled = false - cfg.CrossApplicationTracer.Enabled = false - run = newAppRun(cfg, internal.ConnectReplyDefaults()) - if enabled := run.Config.CrossApplicationTracer.Enabled; enabled { - t.Error(enabled) - } - - cfg = NewConfig("my app", "0123456789012345678901234567890123456789") - cfg.DistributedTracer.Enabled = false - cfg.CrossApplicationTracer.Enabled = true - run = newAppRun(cfg, internal.ConnectReplyDefaults()) - if enabled := run.Config.CrossApplicationTracer.Enabled; !enabled { - t.Error(enabled) - } -} - -func TestTxnTraceThreshold(t *testing.T) { - // Test that the default txn trace threshold is the failing apdex. - cfg := NewConfig("my app", "0123456789012345678901234567890123456789") - run := newAppRun(cfg, internal.ConnectReplyDefaults()) - threshold := run.txnTraceThreshold(1 * time.Second) - if threshold != 4*time.Second { - t.Error(threshold) - } - - // Test that the trace threshold can be assigned to a fixed value. - cfg = NewConfig("my app", "0123456789012345678901234567890123456789") - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 3 * time.Second - run = newAppRun(cfg, internal.ConnectReplyDefaults()) - threshold = run.txnTraceThreshold(1 * time.Second) - if threshold != 3*time.Second { - t.Error(threshold) - } - - // Test that the trace threshold can be overwritten by server-side-config. - // with "apdex_f". - cfg = NewConfig("my app", "0123456789012345678901234567890123456789") - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 3 * time.Second - reply := internal.ConnectReplyDefaults() - json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.transaction_threshold":"apdex_f"}}`), &reply) - run = newAppRun(cfg, reply) - threshold = run.txnTraceThreshold(1 * time.Second) - if threshold != 4*time.Second { - t.Error(threshold) - } - - // Test that the trace threshold can be overwritten by server-side-config. - // with a numberic value. - cfg = NewConfig("my app", "0123456789012345678901234567890123456789") - reply = internal.ConnectReplyDefaults() - json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.transaction_threshold":3}}`), &reply) - run = newAppRun(cfg, reply) - threshold = run.txnTraceThreshold(1 * time.Second) - if threshold != 3*time.Second { - t.Error(threshold) - } -} - -var cfg = NewConfig("name", "license") - -func TestEmptyReplyEventHarvestDefaults(t *testing.T) { - var run internal.HarvestConfigurer = newAppRun(cfg, &internal.ConnectReply{}) - assertHarvestConfig(t, &run, expectHarvestConfig{ - maxTxnEvents: internal.MaxTxnEvents, - maxCustomEvents: internal.MaxCustomEvents, - maxErrorEvents: internal.MaxErrorEvents, - maxSpanEvents: internal.MaxSpanEvents, - periods: map[internal.HarvestTypes]time.Duration{ - internal.HarvestTypesAll: 60 * time.Second, - 0: 60 * time.Second, - }, - }) -} - -func TestEventHarvestFieldsAllPopulated(t *testing.T) { - reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{ - "event_harvest_config": { - "report_period_ms": 5000, - "harvest_limits": { - "analytic_event_data": 1, - "custom_event_data": 2, - "span_event_data": 3, - "error_event_data": 4 - } - } - }}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - var run internal.HarvestConfigurer = newAppRun(cfg, reply) - assertHarvestConfig(t, &run, expectHarvestConfig{ - maxTxnEvents: 1, - maxCustomEvents: 2, - maxErrorEvents: 4, - maxSpanEvents: 3, - periods: map[internal.HarvestTypes]time.Duration{ - internal.HarvestMetricsTraces: 60 * time.Second, - internal.HarvestTypesEvents: 5 * time.Second, - }, - }) -} - -func TestZeroReportPeriod(t *testing.T) { - reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{ - "event_harvest_config": { - "report_period_ms": 0 - } - }}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - var run internal.HarvestConfigurer = newAppRun(cfg, reply) - assertHarvestConfig(t, &run, expectHarvestConfig{ - maxTxnEvents: internal.MaxTxnEvents, - maxCustomEvents: internal.MaxCustomEvents, - maxErrorEvents: internal.MaxErrorEvents, - maxSpanEvents: internal.MaxSpanEvents, - periods: map[internal.HarvestTypes]time.Duration{ - internal.HarvestTypesAll: 60 * time.Second, - 0: 60 * time.Second, - }, - }) -} - -func TestEventHarvestFieldsOnlySpanEvents(t *testing.T) { - reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{ - "event_harvest_config": { - "report_period_ms": 5000, - "harvest_limits": { "span_event_data": 3 } - }}}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - var run internal.HarvestConfigurer = newAppRun(cfg, reply) - assertHarvestConfig(t, &run, expectHarvestConfig{ - maxTxnEvents: internal.MaxTxnEvents, - maxCustomEvents: internal.MaxCustomEvents, - maxErrorEvents: internal.MaxErrorEvents, - maxSpanEvents: 3, - periods: map[internal.HarvestTypes]time.Duration{ - internal.HarvestTypesAll ^ internal.HarvestSpanEvents: 60 * time.Second, - internal.HarvestSpanEvents: 5 * time.Second, - }, - }) -} - -func TestEventHarvestFieldsOnlyTxnEvents(t *testing.T) { - reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{ - "event_harvest_config": { - "report_period_ms": 5000, - "harvest_limits": { "analytic_event_data": 3 } - }}}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - var run internal.HarvestConfigurer = newAppRun(cfg, reply) - assertHarvestConfig(t, &run, expectHarvestConfig{ - maxTxnEvents: 3, - maxCustomEvents: internal.MaxCustomEvents, - maxErrorEvents: internal.MaxErrorEvents, - maxSpanEvents: internal.MaxSpanEvents, - periods: map[internal.HarvestTypes]time.Duration{ - internal.HarvestTypesAll ^ internal.HarvestTxnEvents: 60 * time.Second, - internal.HarvestTxnEvents: 5 * time.Second, - }, - }) -} - -func TestEventHarvestFieldsOnlyErrorEvents(t *testing.T) { - reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{ - "event_harvest_config": { - "report_period_ms": 5000, - "harvest_limits": { "error_event_data": 3 } - }}}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - var run internal.HarvestConfigurer = newAppRun(cfg, reply) - assertHarvestConfig(t, &run, expectHarvestConfig{ - maxTxnEvents: internal.MaxTxnEvents, - maxCustomEvents: internal.MaxCustomEvents, - maxErrorEvents: 3, - maxSpanEvents: internal.MaxSpanEvents, - periods: map[internal.HarvestTypes]time.Duration{ - internal.HarvestTypesAll ^ internal.HarvestErrorEvents: 60 * time.Second, - internal.HarvestErrorEvents: 5 * time.Second, - }, - }) -} - -func TestEventHarvestFieldsOnlyCustomEvents(t *testing.T) { - reply, err := internal.ConstructConnectReply([]byte(`{"return_value":{ - "event_harvest_config": { - "report_period_ms": 5000, - "harvest_limits": { "custom_event_data": 3 } - }}}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - var run internal.HarvestConfigurer = newAppRun(cfg, reply) - assertHarvestConfig(t, &run, expectHarvestConfig{ - maxTxnEvents: internal.MaxTxnEvents, - maxCustomEvents: 3, - maxErrorEvents: internal.MaxErrorEvents, - maxSpanEvents: internal.MaxSpanEvents, - periods: map[internal.HarvestTypes]time.Duration{ - internal.HarvestTypesAll ^ internal.HarvestCustomEvents: 60 * time.Second, - internal.HarvestCustomEvents: 5 * time.Second, - }, - }) -} - -func TestConfigurableHarvestNegativeReportPeriod(t *testing.T) { - h, err := internal.ConstructConnectReply([]byte(`{"return_value":{ - "event_harvest_config": { - "report_period_ms": -1 - }}}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - expect := time.Duration(internal.DefaultConfigurableEventHarvestMs) * time.Millisecond - if period := h.ConfigurablePeriod(); period != expect { - t.Fatal(expect, period) - } -} - -func TestReplyTraceIDGenerator(t *testing.T) { - // Test that the default connect reply has a populated trace id - // generator that works. - reply := internal.ConnectReplyDefaults() - id1 := reply.TraceIDGenerator.GenerateTraceID() - id2 := reply.TraceIDGenerator.GenerateTraceID() - if len(id1) != 16 || len(id2) != 16 || id1 == id2 { - t.Error(id1, id2) - } -} - -func TestConfigurableTxnEvents_withCollResponse(t *testing.T) { - h, err := internal.ConstructConnectReply([]byte( - `{"return_value":{ - "event_harvest_config": { - "report_period_ms": 10000, - "harvest_limits": { - "analytic_event_data": 15 - } - } - }}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - result := newAppRun(cfg, h).MaxTxnEvents() - if result != 15 { - t.Error(fmt.Sprintf("Unexpected max number of txn events, expected %d but got %d", 15, result)) - } -} - -func TestConfigurableTxnEvents_notInCollResponse(t *testing.T) { - reply, err := internal.ConstructConnectReply([]byte( - `{"return_value":{ - "event_harvest_config": { - "report_period_ms": 10000 - } - }}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - expected := 10 - cfg.TransactionEvents.MaxSamplesStored = expected - result := newAppRun(cfg, reply).MaxTxnEvents() - if result != expected { - t.Error(fmt.Sprintf("Unexpected max number of txn events, expected %d but got %d", expected, result)) - } -} - -func TestConfigurableTxnEvents_configMoreThanMax(t *testing.T) { - h, err := internal.ConstructConnectReply([]byte( - `{"return_value":{ - "event_harvest_config": { - "report_period_ms": 10000 - } - }}`), internal.PreconnectReply{}) - if nil != err { - t.Fatal(err) - } - cfg.TransactionEvents.MaxSamplesStored = internal.MaxTxnEvents + 100 - result := newAppRun(cfg, h).MaxTxnEvents() - if result != internal.MaxTxnEvents { - t.Error(fmt.Sprintf("Unexpected max number of txn events, expected %d but got %d", internal.MaxTxnEvents, result)) - } -} - -type expectHarvestConfig struct { - maxTxnEvents int - maxCustomEvents int - maxErrorEvents int - maxSpanEvents int - periods map[internal.HarvestTypes]time.Duration -} - -func assertHarvestConfig(t testing.TB, hc *internal.HarvestConfigurer, expect expectHarvestConfig) { - if h, ok := t.(interface { - Helper() - }); ok { - h.Helper() - } - if max := (*hc).MaxTxnEvents(); max != expect.maxTxnEvents { - t.Error(max, expect.maxTxnEvents) - } - if max := (*hc).MaxCustomEvents(); max != expect.maxCustomEvents { - t.Error(max, expect.maxCustomEvents) - } - if max := (*hc).MaxSpanEvents(); max != expect.maxSpanEvents { - t.Error(max, expect.maxSpanEvents) - } - if max := (*hc).MaxErrorEvents(); max != expect.maxErrorEvents { - t.Error(max, expect.maxErrorEvents) - } - if periods := (*hc).ReportPeriods(); !reflect.DeepEqual(periods, expect.periods) { - t.Error(periods, expect.periods) - } -} diff --git a/application.go b/application.go deleted file mode 100644 index f1b4ea264..000000000 --- a/application.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "net/http" - "time" -) - -// Application represents your application. -type Application interface { - // StartTransaction begins a Transaction. - // * Transaction.NewGoroutine() must be used to pass the Transaction - // between goroutines. - // * This method never returns nil. - // * The Transaction is considered a web transaction if an http.Request - // is provided. - // * The transaction returned implements the http.ResponseWriter - // interface. Provide your ResponseWriter as a parameter and - // then use the Transaction in its place to instrument the response - // code and response headers. - StartTransaction(name string, w http.ResponseWriter, r *http.Request) Transaction - - // RecordCustomEvent adds a custom event. - // - // eventType must consist of alphanumeric characters, underscores, and - // colons, and must contain fewer than 255 bytes. - // - // Each value in the params map must be a number, string, or boolean. - // Keys must be less than 255 bytes. The params map may not contain - // more than 64 attributes. For more information, and a set of - // restricted keywords, see: - // - // https://docs.newrelic.com/docs/insights/new-relic-insights/adding-querying-data/inserting-custom-events-new-relic-apm-agents - // - // An error is returned if event type or params is invalid. - RecordCustomEvent(eventType string, params map[string]interface{}) error - - // RecordCustomMetric records a custom metric. The metric name you - // provide will be prefixed by "Custom/". Custom metrics are not - // currently supported in serverless mode. - // - // https://docs.newrelic.com/docs/agents/manage-apm-agents/agent-data/collect-custom-metrics - RecordCustomMetric(name string, value float64) error - - // WaitForConnection blocks until the application is connected, is - // incapable of being connected, or the timeout has been reached. This - // method is useful for short-lived processes since the application will - // not gather data until it is connected. nil is returned if the - // application is connected successfully. - WaitForConnection(timeout time.Duration) error - - // Shutdown flushes data to New Relic's servers and stops all - // agent-related goroutines managing this application. After Shutdown - // is called, The application is disabled and will never collect data - // again. This method blocks until all final data is sent to New Relic - // or the timeout has elapsed. Increase the timeout and check debug - // logs if you aren't seeing data. - Shutdown(timeout time.Duration) -} - -// NewApplication creates an Application and spawns goroutines to manage the -// aggregation and harvesting of data. On success, a non-nil Application and a -// nil error are returned. On failure, a nil Application and a non-nil error -// are returned. Applications do not share global state, therefore it is safe -// to create multiple applications. -func NewApplication(c Config) (Application, error) { - return newApp(c) -} diff --git a/attributes.go b/attributes.go deleted file mode 100644 index f4966d195..000000000 --- a/attributes.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -// This file contains the names of the automatically captured attributes. -// Attributes are key value pairs attached to transaction events, error events, -// and traced errors. You may add your own attributes using the -// Transaction.AddAttribute method (see transaction.go). -// -// These attribute names are exposed here to facilitate configuration. -// -// For more information, see: -// https://docs.newrelic.com/docs/agents/manage-apm-agents/agent-metrics/agent-attributes - -// Attributes destined for Transaction Events, Errors, and Transaction Traces: -const ( - // AttributeResponseCode is the response status code for a web request. - AttributeResponseCode = "httpResponseCode" - // AttributeRequestMethod is the request's method. - AttributeRequestMethod = "request.method" - // AttributeRequestAccept is the request's "Accept" header. - AttributeRequestAccept = "request.headers.accept" - // AttributeRequestContentType is the request's "Content-Type" header. - AttributeRequestContentType = "request.headers.contentType" - // AttributeRequestContentLength is the request's "Content-Length" header. - AttributeRequestContentLength = "request.headers.contentLength" - // AttributeRequestHost is the request's "Host" header. - AttributeRequestHost = "request.headers.host" - // AttributeRequestURI is the request's URL without query parameters, - // fragment, user, or password. - AttributeRequestURI = "request.uri" - // AttributeResponseContentType is the response "Content-Type" header. - AttributeResponseContentType = "response.headers.contentType" - // AttributeResponseContentLength is the response "Content-Length" header. - AttributeResponseContentLength = "response.headers.contentLength" - // AttributeHostDisplayName contains the value of Config.HostDisplayName. - AttributeHostDisplayName = "host.displayName" -) - -// Attributes destined for Errors and Transaction Traces: -const ( - // AttributeRequestUserAgent is the request's "User-Agent" header. - AttributeRequestUserAgent = "request.headers.User-Agent" - // AttributeRequestReferer is the request's "Referer" header. Query - // string parameters are removed. - AttributeRequestReferer = "request.headers.referer" -) - -// AWS Lambda specific attributes: -const ( - AttributeAWSRequestID = "aws.requestId" - AttributeAWSLambdaARN = "aws.lambda.arn" - AttributeAWSLambdaColdStart = "aws.lambda.coldStart" - AttributeAWSLambdaEventSourceARN = "aws.lambda.eventSource.arn" -) - -// Attributes for consumed message transactions: -// -// When a message is consumed (for example from Kafka or RabbitMQ), supported -// instrumentation packages -- i.e. those found in the _integrations -// (https://godoc.org/github.com/newrelic/go-agent/_integrations) directory -- -// will add these attributes automatically. `AttributeMessageExchangeType`, -// `AttributeMessageReplyTo`, and `AttributeMessageCorrelationID` are disabled -// by default. To see these attributes added to all destinations, you must add -// include them in your config settings: -// -// cfg.Attributes.Include = append(cfg.Attributes.Include, -// AttributeMessageExchangeType, AttributeMessageReplyTo, -// AttributeMessageCorrelationID) -// -// When not using a supported instrumentation package, you can add these -// attributes manually using the `Transaction.AddAttribute` -// (https://godoc.org/github.com/newrelic/go-agent#Transaction) API. In this -// case, these attributes will be included on all destintations by default. -// -// txn := app.StartTransaction("Message/RabbitMQ/Exchange/Named/MyExchange", nil, nil) -// txn.AddAttribute(AttributeMessageRoutingKey, "myRoutingKey") -// txn.AddAttribute(AttributeMessageQueueName, "myQueueName") -// txn.AddAttribute(AttributeMessageExchangeType, "myExchangeType") -// txn.AddAttribute(AttributeMessageReplyTo, "myReplyTo") -// txn.AddAttribute(AttributeMessageCorrelationID, "myCorrelationID") -// // ... consume a message ... -// txn.End() -// -// It is recommended that at most one message is consumed per transaction. -const ( - // The routing key of the consumed message. - AttributeMessageRoutingKey = "message.routingKey" - // The name of the queue the message was consumed from. - AttributeMessageQueueName = "message.queueName" - // The type of exchange used for the consumed message (direct, fanout, - // topic, or headers). - AttributeMessageExchangeType = "message.exchangeType" - // The callback queue used in RPC configurations. - AttributeMessageReplyTo = "message.replyTo" - // The application-generated identifier used in RPC configurations. - AttributeMessageCorrelationID = "message.correlationId" -) - -// Attributes destined for Span Events: -// -// To disable the capture of one of these span event attributes, db.statement -// for example, modify your Config like this: -// -// cfg.SpanEvents.Attributes.Exclude = append(cfg.SpanEvents.Attributes.Exclude, -// newrelic.SpanAttributeDBStatement) -const ( - SpanAttributeDBStatement = "db.statement" - SpanAttributeDBInstance = "db.instance" - SpanAttributeDBCollection = "db.collection" - SpanAttributePeerAddress = "peer.address" - SpanAttributePeerHostname = "peer.hostname" - SpanAttributeHTTPURL = "http.url" - SpanAttributeHTTPMethod = "http.method" - SpanAttributeAWSOperation = "aws.operation" - SpanAttributeAWSRequestID = "aws.requestId" - SpanAttributeAWSRegion = "aws.region" -) diff --git a/browser_header.go b/browser_header.go deleted file mode 100644 index c20c86b56..000000000 --- a/browser_header.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/json" -) - -var ( - browserStartTag = []byte(``) - browserInfoPrefix = []byte(`window.NREUM||(NREUM={});NREUM.info=`) -) - -// browserInfo contains the fields that are marshalled into the Browser agent's -// info hash. -// -// https://newrelic.atlassian.net/wiki/spaces/eng/pages/50299103/BAM+Agent+Auto-Instrumentation -type browserInfo struct { - Beacon string `json:"beacon"` - LicenseKey string `json:"licenseKey"` - ApplicationID string `json:"applicationID"` - TransactionName string `json:"transactionName"` - QueueTimeMillis int64 `json:"queueTime"` - ApplicationTimeMillis int64 `json:"applicationTime"` - ObfuscatedAttributes string `json:"atts"` - ErrorBeacon string `json:"errorBeacon"` - Agent string `json:"agent"` -} - -// BrowserTimingHeader encapsulates the JavaScript required to enable New -// Relic's Browser product. -type BrowserTimingHeader struct { - agentLoader string - info browserInfo -} - -func appendSlices(slices ...[]byte) []byte { - length := 0 - for _, s := range slices { - length += len(s) - } - combined := make([]byte, 0, length) - for _, s := range slices { - combined = append(combined, s...) - } - return combined -} - -// WithTags returns the browser timing JavaScript which includes the enclosing -// tags. This method returns nil if the receiver is -// nil, the feature is disabled, the application is not yet connected, or an -// error occurs. The byte slice returned is in UTF-8 format. -func (h *BrowserTimingHeader) WithTags() []byte { - withoutTags := h.WithoutTags() - if nil == withoutTags { - return nil - } - return appendSlices(browserStartTag, withoutTags, browserEndTag) -} - -// WithoutTags returns the browser timing JavaScript without any enclosing tags, -// which may then be embedded within any JavaScript code. This method returns -// nil if the receiver is nil, the feature is disabled, the application is not -// yet connected, or an error occurs. The byte slice returned is in UTF-8 -// format. -func (h *BrowserTimingHeader) WithoutTags() []byte { - if nil == h { - return nil - } - - // We could memoise this, but it seems unnecessary, since most users are - // going to call this zero or one times. - info, err := json.Marshal(h.info) - if err != nil { - // There's no way to log from here, but this also should be unreachable in - // practice. - return nil - } - - return appendSlices([]byte(h.agentLoader), browserInfoPrefix, info) -} diff --git a/browser_header_test.go b/browser_header_test.go deleted file mode 100644 index 4647ea8d9..000000000 --- a/browser_header_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "fmt" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -func TestNilBrowserTimingHeader(t *testing.T) { - var h *BrowserTimingHeader - - // The methods on a nil BrowserTimingHeader pointer should not panic. - - if out := h.WithTags(); out != nil { - t.Errorf("unexpected WithTags output for a disabled header: expected a blank string; got %s", out) - } - - if out := h.WithoutTags(); out != nil { - t.Errorf("unexpected WithoutTags output for a disabled header: expected a blank string; got %s", out) - } -} - -func TestEnabled(t *testing.T) { - // We're not trying to test Go's JSON marshalling here; we just want to - // ensure that we get the right fields out the other side. - expectInfo := internal.CompactJSONString(` - { - "beacon": "brecon", - "licenseKey": "12345", - "applicationID": "app", - "transactionName": "txn", - "queueTime": 1, - "applicationTime": 2, - "atts": "attrs", - "errorBeacon": "blah", - "agent": "bond" - } - `) - - h := &BrowserTimingHeader{ - agentLoader: "loader();", - info: browserInfo{ - Beacon: "brecon", - LicenseKey: "12345", - ApplicationID: "app", - TransactionName: "txn", - QueueTimeMillis: 1, - ApplicationTimeMillis: 2, - ObfuscatedAttributes: "attrs", - ErrorBeacon: "blah", - Agent: "bond", - }, - } - - expected := fmt.Sprintf("%s%s%s%s%s", browserStartTag, h.agentLoader, browserInfoPrefix, expectInfo, browserEndTag) - if actual := h.WithTags(); string(actual) != expected { - t.Errorf("unexpected WithTags output: expected %s; got %s", expected, string(actual)) - } - - expected = fmt.Sprintf("%s%s%s", h.agentLoader, browserInfoPrefix, expectInfo) - if actual := h.WithoutTags(); string(actual) != expected { - t.Errorf("unexpected WithoutTags output: expected %s; got %s", expected, string(actual)) - } -} diff --git a/config.go b/config.go deleted file mode 100644 index 85caca8fa..000000000 --- a/config.go +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "errors" - "fmt" - "net/http" - "strings" - "time" - - "github.com/newrelic/go-agent/internal" -) - -// Config contains Application and Transaction behavior settings. -// Use NewConfig to create a Config with proper defaults. -type Config struct { - // AppName is used by New Relic to link data across servers. - // - // https://docs.newrelic.com/docs/apm/new-relic-apm/installation-configuration/naming-your-application - AppName string - - // License is your New Relic license key. - // - // https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/license-key - License string - - // Logger controls go-agent logging. For info level logging to stdout: - // - // cfg.Logger = newrelic.NewLogger(os.Stdout) - // - // For debug level logging to stdout: - // - // cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - // - // See https://github.com/newrelic/go-agent/blob/master/GUIDE.md#logging - // for more examples and logging integrations. - Logger Logger - - // Enabled controls whether the agent will communicate with the New Relic - // servers and spawn goroutines. Setting this to be false is useful in - // testing and staging situations. - Enabled bool - - // Labels are key value pairs used to roll up applications into specific - // categories. - // - // https://docs.newrelic.com/docs/using-new-relic/user-interface-functions/organize-your-data/labels-categories-organize-apps-monitors - Labels map[string]string - - // HighSecurity guarantees that certain agent settings can not be made - // more permissive. This setting must match the corresponding account - // setting in the New Relic UI. - // - // https://docs.newrelic.com/docs/agents/manage-apm-agents/configuration/high-security-mode - HighSecurity bool - - // SecurityPoliciesToken enables security policies if set to a non-empty - // string. Only set this if security policies have been enabled on your - // account. This cannot be used in conjunction with HighSecurity. - // - // https://docs.newrelic.com/docs/agents/manage-apm-agents/configuration/enable-configurable-security-policies - SecurityPoliciesToken string - - // CustomInsightsEvents controls the behavior of - // Application.RecordCustomEvent. - // - // https://docs.newrelic.com/docs/insights/new-relic-insights/adding-querying-data/inserting-custom-events-new-relic-apm-agents - CustomInsightsEvents struct { - // Enabled controls whether RecordCustomEvent will collect - // custom analytics events. High security mode overrides this - // setting. - Enabled bool - } - - // TransactionEvents controls the behavior of transaction analytics - // events. - TransactionEvents struct { - // Enabled controls whether transaction events are captured. - Enabled bool - // Attributes controls the attributes included with transaction - // events. - Attributes AttributeDestinationConfig - // MaxSamplesStored allows you to limit the number of Transaction - // Events stored/reported in a given 60-second period - MaxSamplesStored int - } - - // ErrorCollector controls the capture of errors. - ErrorCollector struct { - // Enabled controls whether errors are captured. This setting - // affects both traced errors and error analytics events. - Enabled bool - // CaptureEvents controls whether error analytics events are - // captured. - CaptureEvents bool - // IgnoreStatusCodes controls which http response codes are - // automatically turned into errors. By default, response codes - // greater than or equal to 400, with the exception of 404, are - // turned into errors. - IgnoreStatusCodes []int - // Attributes controls the attributes included with errors. - Attributes AttributeDestinationConfig - } - - // TransactionTracer controls the capture of transaction traces. - TransactionTracer struct { - // Enabled controls whether transaction traces are captured. - Enabled bool - // Threshold controls whether a transaction trace will be - // considered for capture. Of the traces exceeding the - // threshold, the slowest trace every minute is captured. - Threshold struct { - // If IsApdexFailing is true then the trace threshold is - // four times the apdex threshold. - IsApdexFailing bool - // If IsApdexFailing is false then this field is the - // threshold, otherwise it is ignored. - Duration time.Duration - } - // SegmentThreshold is the threshold at which segments will be - // added to the trace. Lowering this setting may increase - // overhead. Decrease this duration if your Transaction Traces are - // missing segments. - SegmentThreshold time.Duration - // StackTraceThreshold is the threshold at which segments will - // be given a stack trace in the transaction trace. Lowering - // this setting will increase overhead. - StackTraceThreshold time.Duration - // Attributes controls the attributes included with transaction - // traces. - Attributes AttributeDestinationConfig - // Segments.Attributes controls the attributes included with - // each trace segment. - Segments struct { - Attributes AttributeDestinationConfig - } - } - - // BrowserMonitoring contains settings which control the behavior of - // Transaction.BrowserTimingHeader. - BrowserMonitoring struct { - // Enabled controls whether or not the Browser monitoring feature is - // enabled. - Enabled bool - // Attributes controls the attributes included with Browser monitoring. - // BrowserMonitoring.Attributes.Enabled is false by default, to include - // attributes in the Browser timing Javascript: - // - // cfg.BrowserMonitoring.Attributes.Enabled = true - Attributes AttributeDestinationConfig - } - - // HostDisplayName gives this server a recognizable name in the New - // Relic UI. This is an optional setting. - HostDisplayName string - - // Transport customizes communication with the New Relic servers. This may - // be used to configure a proxy. - Transport http.RoundTripper - - // Utilization controls the detection and gathering of system - // information. - Utilization struct { - // DetectAWS controls whether the Application attempts to detect - // AWS. - DetectAWS bool - // DetectAzure controls whether the Application attempts to detect - // Azure. - DetectAzure bool - // DetectPCF controls whether the Application attempts to detect - // PCF. - DetectPCF bool - // DetectGCP controls whether the Application attempts to detect - // GCP. - DetectGCP bool - // DetectDocker controls whether the Application attempts to - // detect Docker. - DetectDocker bool - // DetectKubernetes controls whether the Application attempts to - // detect Kubernetes. - DetectKubernetes bool - - // These settings provide system information when custom values - // are required. - LogicalProcessors int - TotalRAMMIB int - BillingHostname string - } - - // CrossApplicationTracer controls behaviour relating to cross application - // tracing (CAT), available since Go Agent v0.11. The - // CrossApplicationTracer and the DistributedTracer cannot be - // simultaneously enabled. - // - // https://docs.newrelic.com/docs/apm/transactions/cross-application-traces/introduction-cross-application-traces - CrossApplicationTracer struct { - Enabled bool - } - - // DistributedTracer controls behaviour relating to Distributed Tracing, - // available since Go Agent v2.1. The DistributedTracer and the - // CrossApplicationTracer cannot be simultaneously enabled. - // - // https://docs.newrelic.com/docs/apm/distributed-tracing/getting-started/introduction-distributed-tracing - DistributedTracer struct { - Enabled bool - } - - // SpanEvents controls behavior relating to Span Events. Span Events - // require that DistributedTracer is enabled. - SpanEvents struct { - Enabled bool - Attributes AttributeDestinationConfig - } - - // DatastoreTracer controls behavior relating to datastore segments. - DatastoreTracer struct { - // InstanceReporting controls whether the host and port are collected - // for datastore segments. - InstanceReporting struct { - Enabled bool - } - // DatabaseNameReporting controls whether the database name is - // collected for datastore segments. - DatabaseNameReporting struct { - Enabled bool - } - QueryParameters struct { - Enabled bool - } - // SlowQuery controls the capture of slow query traces. Slow - // query traces show you instances of your slowest datastore - // segments. - SlowQuery struct { - Enabled bool - Threshold time.Duration - } - } - - // Attributes controls which attributes are enabled and disabled globally. - // This setting affects all attribute destinations: Transaction Events, - // Error Events, Transaction Traces and segments, Traced Errors, Span - // Events, and Browser timing header. - Attributes AttributeDestinationConfig - - // RuntimeSampler controls the collection of runtime statistics like - // CPU/Memory usage, goroutine count, and GC pauses. - RuntimeSampler struct { - // Enabled controls whether runtime statistics are captured. - Enabled bool - } - - // ServerlessMode contains fields which control behavior when running in - // AWS Lambda. - // - // https://docs.newrelic.com/docs/serverless-function-monitoring/aws-lambda-monitoring/get-started/introduction-new-relic-monitoring-aws-lambda - ServerlessMode struct { - // Enabling ServerlessMode will print each transaction's data to - // stdout. No agent goroutines will be spawned in serverless mode, and - // no data will be sent directly to the New Relic backend. - // nrlambda.NewConfig sets Enabled to true. - Enabled bool - // ApdexThreshold sets the Apdex threshold when in ServerlessMode. The - // default is 500 milliseconds. nrlambda.NewConfig populates this - // field using the NEW_RELIC_APDEX_T environment variable. - // - // https://docs.newrelic.com/docs/apm/new-relic-apm/apdex/apdex-measure-user-satisfaction - ApdexThreshold time.Duration - // AccountID, TrustedAccountKey, and PrimaryAppID are used for - // distributed tracing in ServerlessMode. AccountID and - // TrustedAccountKey must be populated for distributed tracing to be - // enabled. nrlambda.NewConfig populates these fields using the - // NEW_RELIC_ACCOUNT_ID, NEW_RELIC_TRUSTED_ACCOUNT_KEY, and - // NEW_RELIC_PRIMARY_APPLICATION_ID environment variables. - AccountID string - TrustedAccountKey string - PrimaryAppID string - } -} - -// AttributeDestinationConfig controls the attributes sent to each destination. -// For more information, see: -// https://docs.newrelic.com/docs/agents/manage-apm-agents/agent-data/agent-attributes -type AttributeDestinationConfig struct { - // Enabled controls whether or not this destination will get any - // attributes at all. For example, to prevent any attributes from being - // added to errors, set: - // - // cfg.ErrorCollector.Attributes.Enabled = false - // - Enabled bool - Include []string - // Exclude allows you to prevent the capture of certain attributes. For - // example, to prevent the capture of the request URL attribute - // "request.uri", set: - // - // cfg.Attributes.Exclude = append(cfg.Attributes.Exclude, newrelic.AttributeRequestURI) - // - // The '*' character acts as a wildcard. For example, to prevent the - // capture of all request related attributes, set: - // - // cfg.Attributes.Exclude = append(cfg.Attributes.Exclude, "request.*") - // - Exclude []string -} - -// NewConfig creates a Config populated with default settings and the given -// appname and license. -func NewConfig(appname, license string) Config { - c := Config{} - - c.AppName = appname - c.License = license - c.Enabled = true - c.Labels = make(map[string]string) - c.CustomInsightsEvents.Enabled = true - c.TransactionEvents.Enabled = true - c.TransactionEvents.Attributes.Enabled = true - c.TransactionEvents.MaxSamplesStored = internal.MaxTxnEvents - c.HighSecurity = false - c.ErrorCollector.Enabled = true - c.ErrorCollector.CaptureEvents = true - c.ErrorCollector.IgnoreStatusCodes = []int{ - // https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - 0, // gRPC OK - 5, // gRPC NOT_FOUND - http.StatusNotFound, // 404 - } - c.ErrorCollector.Attributes.Enabled = true - c.Utilization.DetectAWS = true - c.Utilization.DetectAzure = true - c.Utilization.DetectPCF = true - c.Utilization.DetectGCP = true - c.Utilization.DetectDocker = true - c.Utilization.DetectKubernetes = true - c.Attributes.Enabled = true - c.RuntimeSampler.Enabled = true - - c.TransactionTracer.Enabled = true - c.TransactionTracer.Threshold.IsApdexFailing = true - c.TransactionTracer.Threshold.Duration = 500 * time.Millisecond - c.TransactionTracer.SegmentThreshold = 2 * time.Millisecond - c.TransactionTracer.StackTraceThreshold = 500 * time.Millisecond - c.TransactionTracer.Attributes.Enabled = true - c.TransactionTracer.Segments.Attributes.Enabled = true - - c.BrowserMonitoring.Enabled = true - // browser monitoring attributes are disabled by default - c.BrowserMonitoring.Attributes.Enabled = false - - c.CrossApplicationTracer.Enabled = true - c.DistributedTracer.Enabled = false - c.SpanEvents.Enabled = true - c.SpanEvents.Attributes.Enabled = true - - c.DatastoreTracer.InstanceReporting.Enabled = true - c.DatastoreTracer.DatabaseNameReporting.Enabled = true - c.DatastoreTracer.QueryParameters.Enabled = true - c.DatastoreTracer.SlowQuery.Enabled = true - c.DatastoreTracer.SlowQuery.Threshold = 10 * time.Millisecond - - c.ServerlessMode.ApdexThreshold = 500 * time.Millisecond - c.ServerlessMode.Enabled = false - - return c -} - -const ( - licenseLength = 40 - appNameLimit = 3 -) - -// The following errors will be returned if your Config fails to validate. -var ( - errLicenseLen = fmt.Errorf("license length is not %d", licenseLength) - errAppNameMissing = errors.New("string AppName required") - errAppNameLimit = fmt.Errorf("max of %d rollup application names", appNameLimit) - errHighSecurityWithSecurityPolicies = errors.New("SecurityPoliciesToken and HighSecurity are incompatible; please ensure HighSecurity is set to false if SecurityPoliciesToken is a non-empty string and a security policy has been set for your account") -) - -// Validate checks the config for improper fields. If the config is invalid, -// newrelic.NewApplication returns an error. -func (c Config) Validate() error { - if c.Enabled && !c.ServerlessMode.Enabled { - if len(c.License) != licenseLength { - return errLicenseLen - } - } else { - // The License may be empty when the agent is not enabled. - if len(c.License) != licenseLength && len(c.License) != 0 { - return errLicenseLen - } - } - if "" == c.AppName && c.Enabled && !c.ServerlessMode.Enabled { - return errAppNameMissing - } - if c.HighSecurity && "" != c.SecurityPoliciesToken { - return errHighSecurityWithSecurityPolicies - } - if strings.Count(c.AppName, ";") >= appNameLimit { - return errAppNameLimit - } - return nil -} - -// MaxTxnEvents returns the configured maximum number of Transaction Events if it has been configured -// and is less than the default maximum; otherwise it returns the default max. -func (c Config) MaxTxnEvents() int { - configured := c.TransactionEvents.MaxSamplesStored - if configured < 0 || configured > internal.MaxTxnEvents { - return internal.MaxTxnEvents - } - return configured -} diff --git a/context.go b/context.go deleted file mode 100644 index 159bea7d1..000000000 --- a/context.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.7 - -package newrelic - -import ( - "context" - "net/http" - - "github.com/newrelic/go-agent/internal" -) - -// NewContext returns a new Context that carries the provided transaction. -func NewContext(ctx context.Context, txn Transaction) context.Context { - return context.WithValue(ctx, internal.TransactionContextKey, txn) -} - -// FromContext returns the Transaction from the context if present, and nil -// otherwise. -func FromContext(ctx context.Context) Transaction { - h, _ := ctx.Value(internal.TransactionContextKey).(Transaction) - if nil != h { - return h - } - // If we couldn't find a transaction using - // internal.TransactionContextKey, try with - // internal.GinTransactionContextKey. Unfortunately, gin.Context.Set - // requires a string key, so we cannot use - // internal.TransactionContextKey in nrgin.Middleware. We check for two - // keys (rather than turning internal.TransactionContextKey into a - // string key) because context.WithValue will cause golint to complain - // if used with a string key. - h, _ = ctx.Value(internal.GinTransactionContextKey).(Transaction) - return h -} - -// RequestWithTransactionContext adds the transaction to the request's context. -func RequestWithTransactionContext(req *http.Request, txn Transaction) *http.Request { - ctx := req.Context() - ctx = NewContext(ctx, txn) - return req.WithContext(ctx) -} - -func transactionFromRequestContext(req *http.Request) Transaction { - var txn Transaction - if nil != req { - txn = FromContext(req.Context()) - } - return txn -} diff --git a/context_stub.go b/context_stub.go deleted file mode 100644 index b4e05584f..000000000 --- a/context_stub.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build !go1.7 - -package newrelic - -import "net/http" - -// RequestWithTransactionContext adds the transaction to the request's context. -func RequestWithTransactionContext(req *http.Request, txn Transaction) *http.Request { - return req -} - -func transactionFromRequestContext(req *http.Request) Transaction { - return nil -} diff --git a/datastore.go b/datastore.go deleted file mode 100644 index 65f0fbf26..000000000 --- a/datastore.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -// DatastoreProduct is used to identify your datastore type in New Relic. It -// is used in the DatastoreSegment Product field. See -// https://github.com/newrelic/go-agent/blob/master/datastore.go for the full -// list of available DatastoreProducts. -type DatastoreProduct string - -// Datastore names used across New Relic agents: -const ( - DatastoreCassandra DatastoreProduct = "Cassandra" - DatastoreDerby DatastoreProduct = "Derby" - DatastoreElasticsearch DatastoreProduct = "Elasticsearch" - DatastoreFirebird DatastoreProduct = "Firebird" - DatastoreIBMDB2 DatastoreProduct = "IBMDB2" - DatastoreInformix DatastoreProduct = "Informix" - DatastoreMemcached DatastoreProduct = "Memcached" - DatastoreMongoDB DatastoreProduct = "MongoDB" - DatastoreMySQL DatastoreProduct = "MySQL" - DatastoreMSSQL DatastoreProduct = "MSSQL" - DatastoreNeptune DatastoreProduct = "Neptune" - DatastoreOracle DatastoreProduct = "Oracle" - DatastorePostgres DatastoreProduct = "Postgres" - DatastoreRedis DatastoreProduct = "Redis" - DatastoreSolr DatastoreProduct = "Solr" - DatastoreSQLite DatastoreProduct = "SQLite" - DatastoreCouchDB DatastoreProduct = "CouchDB" - DatastoreRiak DatastoreProduct = "Riak" - DatastoreVoltDB DatastoreProduct = "VoltDB" - DatastoreDynamoDB DatastoreProduct = "DynamoDB" - DatastoreAerospike DatastoreProduct = "Aerospike" -) diff --git a/doc.go b/doc.go deleted file mode 100644 index cad90906f..000000000 --- a/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package newrelic provides instrumentation for Go applications. -// -// Deprecated: This package has been supplanted by version 3 here: -// -// https://godoc.org/github.com/newrelic/go-agent/v3/newrelic -package newrelic diff --git a/errors.go b/errors.go deleted file mode 100644 index 93592f4f2..000000000 --- a/errors.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import "github.com/newrelic/go-agent/internal" - -// StackTracer can be implemented by errors to provide a stack trace when using -// Transaction.NoticeError. -type StackTracer interface { - StackTrace() []uintptr -} - -// ErrorClasser can be implemented by errors to provide a custom class when -// using Transaction.NoticeError. -type ErrorClasser interface { - ErrorClass() string -} - -// ErrorAttributer can be implemented by errors to provide extra context when -// using Transaction.NoticeError. -type ErrorAttributer interface { - ErrorAttributes() map[string]interface{} -} - -// Error is an error that implements ErrorClasser, ErrorAttributer, and -// StackTracer. Use it with Transaction.NoticeError to directly control error -// message, class, stacktrace, and attributes. -type Error struct { - // Message is the error message which will be returned by the Error() - // method. - Message string - // Class indicates how the error may be aggregated. - Class string - // Attributes are attached to traced errors and error events for - // additional context. These attributes are validated just like those - // added to `Transaction.AddAttribute`. - Attributes map[string]interface{} - // Stack is the stack trace. Assign this field using NewStackTrace, - // or leave it nil to indicate that Transaction.NoticeError should - // generate one. - Stack []uintptr -} - -// NewStackTrace generates a stack trace which can be assigned to the Error -// struct's Stack field or returned by an error that implements the ErrorClasser -// interface. -func NewStackTrace() []uintptr { - st := internal.GetStackTrace() - return []uintptr(st) -} - -func (e Error) Error() string { return e.Message } - -// ErrorClass implements the ErrorClasser interface. -func (e Error) ErrorClass() string { return e.Class } - -// ErrorAttributes implements the ErrorAttributes interface. -func (e Error) ErrorAttributes() map[string]interface{} { return e.Attributes } - -// StackTrace implements the StackTracer interface. -func (e Error) StackTrace() []uintptr { return e.Stack } diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index acb8934fd..000000000 --- a/examples/README.md +++ /dev/null @@ -1,3 +0,0 @@ -The examples in this directory are for the now deprecated v2 New Relic Go -Agent. Examples for the most recent v3 version of the agent can be found in -[v3/examples](../v3/examples). diff --git a/examples/client-round-tripper/main.go b/examples/client-round-tripper/main.go deleted file mode 100644 index 21f2e0630..000000000 --- a/examples/client-round-tripper/main.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// An application that illustrates Distributed Tracing or Cross Application -// Tracing when using NewRoundTripper. -package main - -import ( - "fmt" - "net/http" - "os" - "time" - - "github.com/newrelic/go-agent" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func doRequest(txn newrelic.Transaction) error { - for _, addr := range []string{"segments", "mysql"} { - url := fmt.Sprintf("http://localhost:8000/%s", addr) - req, err := http.NewRequest("GET", url, nil) - if nil != err { - return err - } - client := &http.Client{} - - // Using NewRoundTripper automatically instruments all request - // for Distributed Tracing and Cross Application Tracing. - client.Transport = newrelic.NewRoundTripper(txn, nil) - - resp, err := client.Do(req) - if nil != err { - return err - } - fmt.Println("response code is", resp.StatusCode) - } - return nil -} - -func main() { - cfg := newrelic.NewConfig("Client App RoundTripper", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - cfg.DistributedTracer.Enabled = true - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - // Wait for the application to connect. - if err = app.WaitForConnection(5 * time.Second); nil != err { - fmt.Println(err) - } - - txn := app.StartTransaction("client-txn", nil, nil) - err = doRequest(txn) - if nil != err { - txn.NoticeError(err) - } - txn.End() - - // Shut down the application to flush data to New Relic. - app.Shutdown(10 * time.Second) -} diff --git a/examples/client/main.go b/examples/client/main.go deleted file mode 100644 index b655bce80..000000000 --- a/examples/client/main.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "net/http" - "os" - "time" - - "github.com/newrelic/go-agent" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func doRequest(txn newrelic.Transaction) error { - req, err := http.NewRequest("GET", "http://localhost:8000/segments", nil) - if nil != err { - return err - } - client := &http.Client{} - seg := newrelic.StartExternalSegment(txn, req) - defer seg.End() - resp, err := client.Do(req) - if nil != err { - return err - } - fmt.Println("response code is", resp.StatusCode) - return nil -} - -func main() { - cfg := newrelic.NewConfig("Client App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - // Wait for the application to connect. - if err = app.WaitForConnection(5 * time.Second); nil != err { - fmt.Println(err) - } - - txn := app.StartTransaction("client-txn", nil, nil) - err = doRequest(txn) - if nil != err { - txn.NoticeError(err) - } - txn.End() - - // Shut down the application to flush data to New Relic. - app.Shutdown(10 * time.Second) -} diff --git a/examples/custom-instrumentation/main.go b/examples/custom-instrumentation/main.go deleted file mode 100644 index ed6969b4b..000000000 --- a/examples/custom-instrumentation/main.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// An application that illustrates Distributed Tracing with custom -// instrumentation. -// -// This application simulates simple inter-process communication between a -// calling and a called process. -// -// Invoked without arguments, the application acts as a calling process; -// invoked with one argument representing a payload, it acts as a called -// process. The calling process creates a payload, starts a called process and -// passes on the payload. The calling process waits until the called process is -// done and then terminates. Thus to start both processes, only a single -// invocation of the application (without any arguments) is needed. -package main - -import ( - "fmt" - "os" - "os/exec" - "time" - - "github.com/newrelic/go-agent" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func called(app newrelic.Application, payload string) { - txn := app.StartTransaction("called-txn", nil, nil) - defer txn.End() - - // Accept the payload that was passed on the command line. - txn.AcceptDistributedTracePayload(newrelic.TransportOther, payload) - time.Sleep(1 * time.Second) -} - -func calling(app newrelic.Application) { - txn := app.StartTransaction("calling-txn", nil, nil) - defer txn.End() - - // Create a payload, start the called process and pass the payload. - payload := txn.CreateDistributedTracePayload() - cmd := exec.Command(os.Args[0], payload.Text()) - cmd.Start() - - // Wait until the called process is done, then exit. - cmd.Wait() - time.Sleep(1 * time.Second) -} - -func makeApplication(name string) (newrelic.Application, error) { - cfg := newrelic.NewConfig(name, mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - - // Distributed Tracing and Cross Application Tracing cannot both be - // enabled at the same time. - cfg.DistributedTracer.Enabled = true - - app, err := newrelic.NewApplication(cfg) - - if nil != err { - return nil, err - } - - // Wait for the application to connect. - if err = app.WaitForConnection(5 * time.Second); nil != err { - return nil, err - } - - return app, nil -} - -func main() { - // Calling processes have no command line arguments, called processes - // have one command line argument (the payload). - isCalled := (len(os.Args) > 1) - - // Initialize the application name. - name := "Go Custom Instrumentation" - if isCalled { - name += " Called" - } else { - name += " Calling" - } - - // Initialize the application. - app, err := makeApplication(name) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - // Run calling/called routines. - if isCalled { - payload := os.Args[1] - called(app, payload) - } else { - calling(app) - } - - // Shut down the application to flush data to New Relic. - app.Shutdown(10 * time.Second) -} diff --git a/examples/server-http/main.go b/examples/server-http/main.go deleted file mode 100644 index d4c39ecae..000000000 --- a/examples/server-http/main.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// An application that illustrates Distributed Tracing or Cross Application -// Tracing when using http.Server or similar frameworks. -package main - -import ( - "fmt" - "io" - "net/http" - "os" - "time" - - newrelic "github.com/newrelic/go-agent" -) - -type handler struct { - App newrelic.Application -} - -func (h *handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { - // The call to StartTransaction must include the response writer and the - // request. - txn := h.App.StartTransaction("server-txn", writer, req) - defer txn.End() - - if req.URL.String() == "/segments" { - defer newrelic.StartSegment(txn, "f1").End() - - func() { - defer newrelic.StartSegment(txn, "f2").End() - - io.WriteString(writer, "segments!") - time.Sleep(10 * time.Millisecond) - }() - time.Sleep(10 * time.Millisecond) - } else { - // Transaction.WriteHeader has to be used instead of invoking - // WriteHeader on the response writer. - txn.WriteHeader(http.StatusNotFound) - } -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func makeApplication() (newrelic.Application, error) { - cfg := newrelic.NewConfig("HTTP Server App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - cfg.DistributedTracer.Enabled = true - app, err := newrelic.NewApplication(cfg) - - if nil != err { - return nil, err - } - - // Wait for the application to connect. - if err = app.WaitForConnection(5 * time.Second); nil != err { - return nil, err - } - - return app, nil -} - -func main() { - - app, err := makeApplication() - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - server := http.Server{ - Addr: ":8000", - Handler: &handler{App: app}, - } - - server.ListenAndServe() -} diff --git a/examples/server/main.go b/examples/server/main.go deleted file mode 100644 index 1bf6a248e..000000000 --- a/examples/server/main.go +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.7 - -package main - -import ( - "errors" - "fmt" - "io" - "log" - "math/rand" - "net/http" - "os" - "sync" - "time" - - newrelic "github.com/newrelic/go-agent" -) - -func index(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "hello world") -} - -func versionHandler(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "New Relic Go Agent Version: "+newrelic.Version) -} - -func noticeError(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "noticing an error") - - if txn := newrelic.FromContext(r.Context()); txn != nil { - txn.NoticeError(errors.New("my error message")) - } -} - -func noticeErrorWithAttributes(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "noticing an error") - - if txn := newrelic.FromContext(r.Context()); txn != nil { - txn.NoticeError(newrelic.Error{ - Message: "uh oh. something went very wrong", - Class: "errors are aggregated by class", - Attributes: map[string]interface{}{ - "important_number": 97232, - "relevant_string": "zap", - }, - }) - } -} - -func customEvent(w http.ResponseWriter, r *http.Request) { - txn := newrelic.FromContext(r.Context()) - - io.WriteString(w, "recording a custom event") - - if nil != txn { - txn.Application().RecordCustomEvent("my_event_type", map[string]interface{}{ - "myString": "hello", - "myFloat": 0.603, - "myInt": 123, - "myBool": true, - }) - } -} - -func setName(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "changing the transaction's name") - - if txn := newrelic.FromContext(r.Context()); txn != nil { - txn.SetName("other-name") - } -} - -func addAttribute(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "adding attributes") - - if txn := newrelic.FromContext(r.Context()); txn != nil { - txn.AddAttribute("myString", "hello") - txn.AddAttribute("myInt", 123) - } -} - -func ignore(w http.ResponseWriter, r *http.Request) { - if coinFlip := (0 == rand.Intn(2)); coinFlip { - if txn := newrelic.FromContext(r.Context()); txn != nil { - txn.Ignore() - } - io.WriteString(w, "ignoring the transaction") - } else { - io.WriteString(w, "not ignoring the transaction") - } -} - -func segments(w http.ResponseWriter, r *http.Request) { - txn := newrelic.FromContext(r.Context()) - - func() { - defer newrelic.StartSegment(txn, "f1").End() - - func() { - defer newrelic.StartSegment(txn, "f2").End() - - io.WriteString(w, "segments!") - time.Sleep(10 * time.Millisecond) - }() - time.Sleep(15 * time.Millisecond) - }() - time.Sleep(20 * time.Millisecond) -} - -func mysql(w http.ResponseWriter, r *http.Request) { - txn := newrelic.FromContext(r.Context()) - s := newrelic.DatastoreSegment{ - StartTime: newrelic.StartSegmentNow(txn), - // Product, Collection, and Operation are the most important - // fields to populate because they are used in the breakdown - // metrics. - Product: newrelic.DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - QueryParameters: map[string]interface{}{ - "name": "Dracula", - "age": 439, - }, - Host: "mysql-server-1", - PortPathOrID: "3306", - DatabaseName: "my_database", - } - defer s.End() - - time.Sleep(20 * time.Millisecond) - io.WriteString(w, `performing fake query "INSERT * from users"`) -} - -func message(w http.ResponseWriter, r *http.Request) { - txn := newrelic.FromContext(r.Context()) - s := newrelic.MessageProducerSegment{ - StartTime: newrelic.StartSegmentNow(txn), - Library: "RabbitMQ", - DestinationType: newrelic.MessageQueue, - DestinationName: "myQueue", - } - defer s.End() - - time.Sleep(20 * time.Millisecond) - io.WriteString(w, `producing a message queue message`) -} - -func external(w http.ResponseWriter, r *http.Request) { - txn := newrelic.FromContext(r.Context()) - req, _ := http.NewRequest("GET", "http://example.com", nil) - - // Using StartExternalSegment is recommended because it does distributed - // tracing header setup, but if you don't have an *http.Request and - // instead only have a url string then you can start the external - // segment like this: - // - // es := newrelic.ExternalSegment{ - // StartTime: newrelic.StartSegmentNow(txn), - // URL: urlString, - // } - // - es := newrelic.StartExternalSegment(txn, req) - resp, err := http.DefaultClient.Do(req) - es.End() - - if nil != err { - io.WriteString(w, err.Error()) - return - } - defer resp.Body.Close() - io.Copy(w, resp.Body) -} - -func roundtripper(w http.ResponseWriter, r *http.Request) { - // NewRoundTripper allows you to instrument external calls without - // calling StartExternalSegment by modifying the http.Client's Transport - // field. If the Transaction parameter is nil, the RoundTripper - // returned will look for a Transaction in the request's context (using - // FromContext). This is recommended because it allows you to reuse the - // same client for multiple transactions. - client := &http.Client{} - client.Transport = newrelic.NewRoundTripper(nil, client.Transport) - - request, _ := http.NewRequest("GET", "http://example.com", nil) - // Since the transaction is already added to the inbound request's - // context by WrapHandleFunc, we just need to copy the context from the - // inbound request to the external request. - request = request.WithContext(r.Context()) - // Alternatively, if you don't want to copy entire context, and instead - // wanted just to add the transaction to the external request's context, - // you could do that like this: - // - // txn := newrelic.FromContext(r.Context()) - // request = newrelic.RequestWithTransactionContext(request, txn) - - resp, err := client.Do(request) - if nil != err { - io.WriteString(w, err.Error()) - return - } - defer resp.Body.Close() - io.Copy(w, resp.Body) -} - -func async(w http.ResponseWriter, r *http.Request) { - txn := newrelic.FromContext(r.Context()) - wg := &sync.WaitGroup{} - wg.Add(1) - go func(txn newrelic.Transaction) { - defer wg.Done() - defer newrelic.StartSegment(txn, "async").End() - time.Sleep(100 * time.Millisecond) - }(txn.NewGoroutine()) - - segment := newrelic.StartSegment(txn, "wg.Wait") - wg.Wait() - segment.End() - w.Write([]byte("done!")) -} - -func customMetric(w http.ResponseWriter, r *http.Request) { - txn := newrelic.FromContext(r.Context()) - for _, vals := range r.Header { - for _, v := range vals { - // This custom metric will have the name - // "Custom/HeaderLength" in the New Relic UI. - if nil != txn { - txn.Application().RecordCustomMetric("HeaderLength", float64(len(v))) - } - } - } - io.WriteString(w, "custom metric recorded") -} - -func browser(w http.ResponseWriter, r *http.Request) { - txn := newrelic.FromContext(r.Context()) - hdr, err := txn.BrowserTimingHeader() - if nil != err { - log.Printf("unable to create browser timing header: %v", err) - } - // BrowserTimingHeader() will always return a header whose methods can - // be safely called. - if js := hdr.WithTags(); js != nil { - w.Write(js) - } - io.WriteString(w, "browser header page") -} - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("Example App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - http.HandleFunc(newrelic.WrapHandleFunc(app, "/", index)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/version", versionHandler)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/notice_error", noticeError)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/notice_error_with_attributes", noticeErrorWithAttributes)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/custom_event", customEvent)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/set_name", setName)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/add_attribute", addAttribute)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/ignore", ignore)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/segments", segments)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/mysql", mysql)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/external", external)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/roundtripper", roundtripper)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/custommetric", customMetric)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/browser", browser)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/async", async)) - http.HandleFunc(newrelic.WrapHandleFunc(app, "/message", message)) - - http.HandleFunc("/background", func(w http.ResponseWriter, req *http.Request) { - // Transactions started without an http.Request are classified as - // background transactions. - txn := app.StartTransaction("background", nil, nil) - defer txn.End() - - io.WriteString(w, "background transaction") - time.Sleep(150 * time.Millisecond) - }) - - http.ListenAndServe(":8000", nil) -} diff --git a/examples/short-lived-process/main.go b/examples/short-lived-process/main.go deleted file mode 100644 index 7f3fd1ac0..000000000 --- a/examples/short-lived-process/main.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "fmt" - "os" - "time" - - "github.com/newrelic/go-agent" -) - -func mustGetEnv(key string) string { - if val := os.Getenv(key); "" != val { - return val - } - panic(fmt.Sprintf("environment variable %s unset", key)) -} - -func main() { - cfg := newrelic.NewConfig("Short Lived App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) - cfg.Logger = newrelic.NewDebugLogger(os.Stdout) - app, err := newrelic.NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - // Wait for the application to connect. - if err := app.WaitForConnection(5 * time.Second); nil != err { - fmt.Println(err) - } - - // Do the tasks at hand. Perhaps record them using transactions and/or - // custom events. - tasks := []string{"white", "black", "red", "blue", "green", "yellow"} - for _, task := range tasks { - txn := app.StartTransaction("task", nil, nil) - time.Sleep(10 * time.Millisecond) - txn.End() - app.RecordCustomEvent("task", map[string]interface{}{ - "color": task, - }) - } - - // Shut down the application to flush data to New Relic. - app.Shutdown(10 * time.Second) -} diff --git a/examples_test.go b/examples_test.go deleted file mode 100644 index 07e823d26..000000000 --- a/examples_test.go +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.7 - -package newrelic - -import ( - "fmt" - "io" - "log" - "net/http" - "net/url" - "os" - "time" -) - -func Example() { - // First create a Config. - cfg := NewConfig("Example Application", "__YOUR_NEW_RELIC_LICENSE_KEY__") - - // Modify Config fields to control agent behavior. - cfg.Logger = NewDebugLogger(os.Stdout) - - // Now use the Config the create an Application. - app, err := NewApplication(cfg) - if nil != err { - fmt.Println(err) - os.Exit(1) - } - - // Now you can use the Application to collect data! Create transactions - // to time inbound requests or background tasks. You can start and stop - // transactions directly using Application.StartTransaction and - // Transaction.End. - func() { - txn := app.StartTransaction("myTask", nil, nil) - defer txn.End() - - time.Sleep(time.Second) - }() - - // WrapHandler and WrapHandleFunc make it easy to instrument inbound web - // requests handled by the http standard library without calling - // StartTransaction. Popular framework instrumentation packages exist - // in the _integrations directory. - http.HandleFunc(WrapHandleFunc(app, "", func(w http.ResponseWriter, req *http.Request) { - io.WriteString(w, "this is the index page") - })) - helloHandler := func(w http.ResponseWriter, req *http.Request) { - // WrapHandler and WrapHandleFunc add the transaction to the - // inbound request's context. Access the transaction using - // FromContext to add attributes, create segments, and notice. - // errors. - txn := FromContext(req.Context()) - - func() { - // Segments help you understand where the time in your - // transaction is being spent. You can use them to time - // functions or arbitrary blocks of code. - defer StartSegment(txn, "helperFunction").End() - }() - - io.WriteString(w, "hello world") - } - http.HandleFunc(WrapHandleFunc(app, "/hello", helloHandler)) - http.ListenAndServe(":8000", nil) -} - -func currentTransaction() Transaction { - return nil -} - -func ExampleNewRoundTripper() { - client := &http.Client{} - // The RoundTripper returned by NewRoundTripper instruments all requests - // done by this client with external segments. - client.Transport = NewRoundTripper(nil, client.Transport) - - request, _ := http.NewRequest("GET", "http://example.com", nil) - - // Be sure to add the current Transaction to each request's context so - // the Transport has access to it. - txn := currentTransaction() - request = RequestWithTransactionContext(request, txn) - - client.Do(request) -} - -func getApp() Application { - return nil -} - -func ExampleBrowserTimingHeader() { - handler := func(w http.ResponseWriter, req *http.Request) { - io.WriteString(w, "") - // The New Relic browser javascript should be placed as high in the - // HTML as possible. We suggest including it immediately after the - // opening tag and any tags. - if txn := FromContext(req.Context()); nil != txn { - hdr, err := txn.BrowserTimingHeader() - if nil != err { - log.Printf("unable to create browser timing header: %v", err) - } - // BrowserTimingHeader() will always return a header whose methods can - // be safely called. - if js := hdr.WithTags(); js != nil { - w.Write(js) - } - } - io.WriteString(w, "browser header page") - } - http.HandleFunc(WrapHandleFunc(getApp(), "/browser", handler)) - http.ListenAndServe(":8000", nil) -} - -func ExampleDatastoreSegment() { - txn := currentTransaction() - ds := &DatastoreSegment{ - StartTime: StartSegmentNow(txn), - // Product, Collection, and Operation are the primary metric - // aggregation fields which we encourage you to populate. - Product: DatastoreMySQL, - Collection: "users_table", - Operation: "SELECT", - } - // your database call here - ds.End() -} - -func ExampleMessageProducerSegment() { - txn := currentTransaction() - seg := &MessageProducerSegment{ - StartTime: StartSegmentNow(txn), - Library: "RabbitMQ", - DestinationType: MessageExchange, - DestinationName: "myExchange", - } - // add message to queue here - seg.End() -} - -func ExampleError() { - txn := currentTransaction() - username := "gopher" - e := fmt.Errorf("error unable to login user %s", username) - // txn.NoticeError(newrelic.Error{...}) instead of txn.NoticeError(e) - // allows more control over error fields. Class is how errors are - // aggregated and Attributes are added to the error event and error - // trace. - txn.NoticeError(Error{ - Message: e.Error(), - Class: "LoginError", - Attributes: map[string]interface{}{ - "username": username, - }, - }) -} - -func ExampleExternalSegment() { - txn := currentTransaction() - client := &http.Client{} - request, _ := http.NewRequest("GET", "http://www.example.com", nil) - segment := StartExternalSegment(txn, request) - response, _ := client.Do(request) - segment.Response = response - segment.End() -} - -// StartExternalSegment is the recommend way of creating ExternalSegments. If -// you don't have access to an http.Request, however, you may create an -// ExternalSegment and control the URL manually. -func ExampleExternalSegment_url() { - txn := currentTransaction() - segment := ExternalSegment{ - StartTime: StartSegmentNow(txn), - // URL is parsed using url.Parse so it must include the protocol - // scheme (eg. "http://"). The host of the URL is used to - // create metrics. Change the host to alter aggregation. - URL: "http://www.example.com", - } - http.Get("http://www.example.com") - segment.End() -} - -func ExampleStartExternalSegment() { - txn := currentTransaction() - client := &http.Client{} - request, _ := http.NewRequest("GET", "http://www.example.com", nil) - segment := StartExternalSegment(txn, request) - response, _ := client.Do(request) - segment.Response = response - segment.End() -} - -func ExampleStartExternalSegment_context() { - txn := currentTransaction() - request, _ := http.NewRequest("GET", "http://www.example.com", nil) - - // If the transaction is added to the request's context then it does not - // need to be provided as a parameter to StartExternalSegment. - request = RequestWithTransactionContext(request, txn) - segment := StartExternalSegment(nil, request) - - client := &http.Client{} - response, _ := client.Do(request) - segment.Response = response - segment.End() -} - -func ExampleNewStaticWebRequest() { - app := getApp() - webReq := NewStaticWebRequest(http.Header{}, &url.URL{Path: "path"}, "GET", TransportHTTP) - txn := app.StartTransaction("My-Transaction", nil, nil) - txn.SetWebRequest(webReq) -} diff --git a/instrumentation.go b/instrumentation.go deleted file mode 100644 index bd3edf783..000000000 --- a/instrumentation.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "net/http" -) - -// instrumentation.go contains helpers built on the lower level api. - -// WrapHandle instruments http.Handler handlers with transactions. To -// instrument this code: -// -// http.Handle("/foo", myHandler) -// -// Perform this replacement: -// -// http.Handle(newrelic.WrapHandle(app, "/foo", myHandler)) -// -// WrapHandle adds the Transaction to the request's context. Access it using -// FromContext to add attributes, create segments, or notice errors: -// -// func myHandler(rw ResponseWriter, req *Request) { -// if txn := newrelic.FromContext(req.Context()); nil != txn { -// txn.AddAttribute("customerLevel", "gold") -// } -// } -// -// This function is safe to call if app is nil. -func WrapHandle(app Application, pattern string, handler http.Handler) (string, http.Handler) { - if app == nil { - return pattern, handler - } - return pattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - txn := app.StartTransaction(pattern, w, r) - defer txn.End() - - r = RequestWithTransactionContext(r, txn) - - handler.ServeHTTP(txn, r) - }) -} - -// WrapHandleFunc instruments handler functions using transactions. To -// instrument this code: -// -// http.HandleFunc("/users", func(w http.ResponseWriter, req *http.Request) { -// io.WriteString(w, "users page") -// }) -// -// Perform this replacement: -// -// http.HandleFunc(WrapHandleFunc(app, "/users", func(w http.ResponseWriter, req *http.Request) { -// io.WriteString(w, "users page") -// })) -// -// WrapHandleFunc adds the Transaction to the request's context. Access it using -// FromContext to add attributes, create segments, or notice errors: -// -// http.HandleFunc(WrapHandleFunc(app, "/users", func(w http.ResponseWriter, req *http.Request) { -// if txn := newrelic.FromContext(req.Context()); nil != txn { -// txn.AddAttribute("customerLevel", "gold") -// } -// io.WriteString(w, "users page") -// })) -// -// This function is safe to call if app is nil. -func WrapHandleFunc(app Application, pattern string, handler func(http.ResponseWriter, *http.Request)) (string, func(http.ResponseWriter, *http.Request)) { - p, h := WrapHandle(app, pattern, http.HandlerFunc(handler)) - return p, func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r) } -} - -// NewRoundTripper creates an http.RoundTripper to instrument external requests -// and add distributed tracing headers. The RoundTripper returned creates an -// external segment before delegating to the original RoundTripper provided (or -// http.DefaultTransport if none is provided). If the Transaction parameter is -// nil then the RoundTripper will look for a Transaction in the request's -// context (using FromContext). Using a nil Transaction is STRONGLY recommended -// because it allows the same RoundTripper (and client) to be reused for -// multiple transactions. -func NewRoundTripper(txn Transaction, original http.RoundTripper) http.RoundTripper { - return roundTripperFunc(func(request *http.Request) (*http.Response, error) { - // The specification of http.RoundTripper requires that the request is never modified. - request = cloneRequest(request) - segment := StartExternalSegment(txn, request) - - if nil == original { - original = http.DefaultTransport - } - response, err := original.RoundTrip(request) - - segment.Response = response - segment.End() - - return response, err - }) -} - -// cloneRequest mimics implementation of -// https://godoc.org/github.com/google/go-github/github#BasicAuthTransport.RoundTrip -func cloneRequest(r *http.Request) *http.Request { - // shallow copy of the struct - r2 := new(http.Request) - *r2 = *r - // deep copy of the Header - r2.Header = make(http.Header, len(r.Header)) - for k, s := range r.Header { - r2.Header[k] = append([]string(nil), s...) - } - return r2 -} - -type roundTripperFunc func(*http.Request) (*http.Response, error) - -func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } diff --git a/internal/adaptive_sampler.go b/internal/adaptive_sampler.go deleted file mode 100644 index 3d45eae4c..000000000 --- a/internal/adaptive_sampler.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "math" - "sync" - "time" -) - -// AdaptiveSampler calculates which transactions should be sampled. An interface -// is used in the connect reply to facilitate testing. -type AdaptiveSampler interface { - ComputeSampled(priority float32, now time.Time) bool -} - -// SampleEverything is used for testing. -type SampleEverything struct{} - -// SampleNothing is used when the application is not yet connected. -type SampleNothing struct{} - -// ComputeSampled implements AdaptiveSampler. -func (s SampleEverything) ComputeSampled(priority float32, now time.Time) bool { return true } - -// ComputeSampled implements AdaptiveSampler. -func (s SampleNothing) ComputeSampled(priority float32, now time.Time) bool { return false } - -type adaptiveSampler struct { - sync.Mutex - period time.Duration - target uint64 - - // Transactions with priority higher than this are sampled. - // This is 1 - sampleRatio. - priorityMin float32 - - currentPeriod struct { - numSampled uint64 - numSeen uint64 - end time.Time - } -} - -// NewAdaptiveSampler creates an AdaptiveSampler. -func NewAdaptiveSampler(period time.Duration, target uint64, now time.Time) AdaptiveSampler { - as := &adaptiveSampler{} - as.period = period - as.target = target - as.currentPeriod.end = now.Add(period) - - // Sample the first transactions in the first period. - as.priorityMin = 0.0 - return as -} - -// ComputeSampled calculates if the transaction should be sampled. -func (as *adaptiveSampler) ComputeSampled(priority float32, now time.Time) bool { - as.Lock() - defer as.Unlock() - - // If the current time is after the end of the "currentPeriod". This is in - // a `for`/`while` loop in case there's a harvest where no sampling happened. - // i.e. for situations where a single call to - // as.currentPeriod.end = as.currentPeriod.end.Add(as.period) - // might not catch us up to the current period - for now.After(as.currentPeriod.end) { - as.priorityMin = 0.0 - if as.currentPeriod.numSeen > 0 { - sampledRatio := float32(as.target) / float32(as.currentPeriod.numSeen) - as.priorityMin = 1.0 - sampledRatio - } - as.currentPeriod.numSampled = 0 - as.currentPeriod.numSeen = 0 - as.currentPeriod.end = as.currentPeriod.end.Add(as.period) - } - - as.currentPeriod.numSeen++ - - // exponential backoff -- if the number of sampled items is greater than our - // target, we need to apply the exponential backoff - if as.currentPeriod.numSampled > as.target { - if as.computeSampledBackoff(as.target, as.currentPeriod.numSeen, as.currentPeriod.numSampled) { - as.currentPeriod.numSampled++ - return true - } - return false - } - - if priority >= as.priorityMin { - as.currentPeriod.numSampled++ - return true - } - - return false -} - -func (as *adaptiveSampler) computeSampledBackoff(target uint64, decidedCount uint64, sampledTrueCount uint64) bool { - return float64(RandUint64N(decidedCount)) < - math.Pow(float64(target), (float64(target)/float64(sampledTrueCount)))-math.Pow(float64(target), 0.5) -} diff --git a/internal/adaptive_sampler_test.go b/internal/adaptive_sampler_test.go deleted file mode 100644 index ae8212991..000000000 --- a/internal/adaptive_sampler_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "testing" - "time" -) - -func assert(t testing.TB, expectTrue bool) { - if h, ok := t.(interface { - Helper() - }); ok { - h.Helper() - } - if !expectTrue { - t.Error(expectTrue) - } -} - -func TestDefaultReplyValidSampler(t *testing.T) { - reply := ConnectReplyDefaults() - assert(t, !reply.AdaptiveSampler.ComputeSampled(1.0, time.Now())) -} - -func TestAdaptiveSampler(t *testing.T) { - start := time.Now() - sampler := NewAdaptiveSampler(60*time.Second, 2, start) - - // first period -- we're guaranteed to get 2 sampled - // due to our target, and we'll send through a total of 4 - assert(t, sampler.ComputeSampled(0.0, start)) - assert(t, sampler.ComputeSampled(0.0, start)) - sampler.ComputeSampled(0.0, start) - sampler.ComputeSampled(0.0, start) - - // Next period! 4 calls in the last period means a new sample ratio - // of 1/2. Nothing with a priority less than the ratio will get through - now := start.Add(61 * time.Second) - assert(t, !sampler.ComputeSampled(0.0, now)) - assert(t, !sampler.ComputeSampled(0.0, now)) - assert(t, !sampler.ComputeSampled(0.0, now)) - assert(t, !sampler.ComputeSampled(0.0, now)) - assert(t, !sampler.ComputeSampled(0.49, now)) - assert(t, !sampler.ComputeSampled(0.49, now)) - - // but these two will get through, and we'll still be under - // our target rate so there's no random sampling to deal with - assert(t, sampler.ComputeSampled(0.55, now)) - assert(t, sampler.ComputeSampled(1.0, now)) - - // Next period! 8 calls in the last period means a new sample ratio - // of 1/4. - now = start.Add(121 * time.Second) - assert(t, !sampler.ComputeSampled(0.0, now)) - assert(t, !sampler.ComputeSampled(0.5, now)) - assert(t, !sampler.ComputeSampled(0.7, now)) - assert(t, sampler.ComputeSampled(0.8, now)) -} - -func TestAdaptiveSamplerSkipPeriod(t *testing.T) { - start := time.Now() - sampler := NewAdaptiveSampler(60*time.Second, 2, start) - - // same as the previous test, we know we can get two through - // and we'll send a total of 4 through - assert(t, sampler.ComputeSampled(0.0, start)) - assert(t, sampler.ComputeSampled(0.0, start)) - sampler.ComputeSampled(0.0, start) - sampler.ComputeSampled(0.0, start) - - // Two periods later! Since there was a period with no samples, priorityMin - // should be zero - - now := start.Add(121 * time.Second) - assert(t, sampler.ComputeSampled(0.0, now)) - assert(t, sampler.ComputeSampled(0.0, now)) -} - -func TestAdaptiveSamplerTarget(t *testing.T) { - var target uint64 - target = 20 - start := time.Now() - sampler := NewAdaptiveSampler(60*time.Second, target, start) - - // we should always sample up to the number of target events - for i := 0; uint64(i) < target; i++ { - assert(t, sampler.ComputeSampled(0.0, start)) - } - - // but now further calls to ComputeSampled are subject to exponential backoff. - // this means their sampling is subject to a bit of randomness and we have no - // guarantee of a true or false sample, just an increasing unlikeliness that - // things will be sampled -} diff --git a/internal/analytics_events.go b/internal/analytics_events.go deleted file mode 100644 index 088d37fa1..000000000 --- a/internal/analytics_events.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "container/heap" - - "github.com/newrelic/go-agent/internal/jsonx" -) - -type analyticsEvent struct { - priority Priority - jsonWriter -} - -type analyticsEventHeap []analyticsEvent - -type analyticsEvents struct { - numSeen int - events analyticsEventHeap - failedHarvests int -} - -func (events *analyticsEvents) NumSeen() float64 { return float64(events.numSeen) } -func (events *analyticsEvents) NumSaved() float64 { return float64(len(events.events)) } - -func (h analyticsEventHeap) Len() int { return len(h) } -func (h analyticsEventHeap) Less(i, j int) bool { return h[i].priority.isLowerPriority(h[j].priority) } -func (h analyticsEventHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } - -// Push and Pop are unused: only heap.Init and heap.Fix are used. -func (h analyticsEventHeap) Push(x interface{}) {} -func (h analyticsEventHeap) Pop() interface{} { return nil } - -func newAnalyticsEvents(max int) *analyticsEvents { - return &analyticsEvents{ - numSeen: 0, - events: make(analyticsEventHeap, 0, max), - failedHarvests: 0, - } -} - -func (events *analyticsEvents) capacity() int { - return cap(events.events) -} - -func (events *analyticsEvents) addEvent(e analyticsEvent) { - events.numSeen++ - - if events.capacity() == 0 { - // Configurable event harvest limits may be zero. - return - } - - if len(events.events) < cap(events.events) { - events.events = append(events.events, e) - if len(events.events) == cap(events.events) { - // Delay heap initialization so that we can have - // deterministic ordering for integration tests (the max - // is not being reached). - heap.Init(events.events) - } - return - } - - if e.priority.isLowerPriority((events.events)[0].priority) { - return - } - - events.events[0] = e - heap.Fix(events.events, 0) -} - -func (events *analyticsEvents) mergeFailed(other *analyticsEvents) { - fails := other.failedHarvests + 1 - if fails >= failedEventsAttemptsLimit { - return - } - events.failedHarvests = fails - events.Merge(other) -} - -func (events *analyticsEvents) Merge(other *analyticsEvents) { - allSeen := events.numSeen + other.numSeen - - for _, e := range other.events { - events.addEvent(e) - } - events.numSeen = allSeen -} - -func (events *analyticsEvents) CollectorJSON(agentRunID string) ([]byte, error) { - if 0 == len(events.events) { - return nil, nil - } - - estimate := 256 * len(events.events) - buf := bytes.NewBuffer(make([]byte, 0, estimate)) - - buf.WriteByte('[') - jsonx.AppendString(buf, agentRunID) - buf.WriteByte(',') - buf.WriteByte('{') - buf.WriteString(`"reservoir_size":`) - jsonx.AppendUint(buf, uint64(cap(events.events))) - buf.WriteByte(',') - buf.WriteString(`"events_seen":`) - jsonx.AppendUint(buf, uint64(events.numSeen)) - buf.WriteByte('}') - buf.WriteByte(',') - buf.WriteByte('[') - for i, e := range events.events { - if i > 0 { - buf.WriteByte(',') - } - e.WriteJSON(buf) - } - buf.WriteByte(']') - buf.WriteByte(']') - - return buf.Bytes(), nil - -} - -// split splits the events into two. NOTE! The two event pools are not valid -// priority queues, and should only be used to create JSON, not for adding any -// events. -func (events *analyticsEvents) split() (*analyticsEvents, *analyticsEvents) { - // numSeen is conserved: e1.numSeen + e2.numSeen == events.numSeen. - e1 := &analyticsEvents{ - numSeen: len(events.events) / 2, - events: make([]analyticsEvent, len(events.events)/2), - failedHarvests: events.failedHarvests, - } - e2 := &analyticsEvents{ - numSeen: events.numSeen - e1.numSeen, - events: make([]analyticsEvent, len(events.events)-len(e1.events)), - failedHarvests: events.failedHarvests, - } - // Note that slicing is not used to ensure that length == capacity for - // e1.events and e2.events. - copy(e1.events, events.events) - copy(e2.events, events.events[len(events.events)/2:]) - - return e1, e2 -} diff --git a/internal/analytics_events_test.go b/internal/analytics_events_test.go deleted file mode 100644 index 876199535..000000000 --- a/internal/analytics_events_test.go +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "strconv" - "testing" - "time" -) - -var ( - agentRunID = `12345` -) - -type priorityWriter Priority - -func (x priorityWriter) WriteJSON(buf *bytes.Buffer) { - buf.WriteString(strconv.FormatFloat(float64(x), 'f', -1, 32)) -} - -func sampleAnalyticsEvent(priority Priority) analyticsEvent { - return analyticsEvent{ - priority, - priorityWriter(priority), - } -} - -func TestBasic(t *testing.T) { - events := newAnalyticsEvents(10) - events.addEvent(sampleAnalyticsEvent(0.5)) - events.addEvent(sampleAnalyticsEvent(0.5)) - events.addEvent(sampleAnalyticsEvent(0.5)) - - json, err := events.CollectorJSON(agentRunID) - if nil != err { - t.Fatal(err) - } - - expected := `["12345",{"reservoir_size":10,"events_seen":3},[0.5,0.5,0.5]]` - - if string(json) != expected { - t.Error(string(json), expected) - } - if 3 != events.numSeen { - t.Error(events.numSeen) - } - if 3 != events.NumSaved() { - t.Error(events.NumSaved()) - } -} - -func TestEmpty(t *testing.T) { - events := newAnalyticsEvents(10) - json, err := events.CollectorJSON(agentRunID) - if nil != err { - t.Fatal(err) - } - if nil != json { - t.Error(string(json)) - } - if 0 != events.numSeen { - t.Error(events.numSeen) - } - if 0 != events.NumSaved() { - t.Error(events.NumSaved()) - } -} - -func TestSampling(t *testing.T) { - events := newAnalyticsEvents(3) - events.addEvent(sampleAnalyticsEvent(0.999999)) - events.addEvent(sampleAnalyticsEvent(0.1)) - events.addEvent(sampleAnalyticsEvent(0.9)) - events.addEvent(sampleAnalyticsEvent(0.2)) - events.addEvent(sampleAnalyticsEvent(0.8)) - events.addEvent(sampleAnalyticsEvent(0.3)) - - json, err := events.CollectorJSON(agentRunID) - if nil != err { - t.Fatal(err) - } - if string(json) != `["12345",{"reservoir_size":3,"events_seen":6},[0.8,0.999999,0.9]]` { - t.Error(string(json)) - } - if 6 != events.numSeen { - t.Error(events.numSeen) - } - if 3 != events.NumSaved() { - t.Error(events.NumSaved()) - } -} - -func TestMergeEmpty(t *testing.T) { - e1 := newAnalyticsEvents(10) - e2 := newAnalyticsEvents(10) - e1.Merge(e2) - json, err := e1.CollectorJSON(agentRunID) - if nil != err { - t.Fatal(err) - } - if nil != json { - t.Error(string(json)) - } - if 0 != e1.numSeen { - t.Error(e1.numSeen) - } - if 0 != e1.NumSaved() { - t.Error(e1.NumSaved()) - } -} - -func TestMergeFull(t *testing.T) { - e1 := newAnalyticsEvents(2) - e2 := newAnalyticsEvents(3) - - e1.addEvent(sampleAnalyticsEvent(0.1)) - e1.addEvent(sampleAnalyticsEvent(0.15)) - e1.addEvent(sampleAnalyticsEvent(0.25)) - - e2.addEvent(sampleAnalyticsEvent(0.06)) - e2.addEvent(sampleAnalyticsEvent(0.12)) - e2.addEvent(sampleAnalyticsEvent(0.18)) - e2.addEvent(sampleAnalyticsEvent(0.24)) - - e1.Merge(e2) - json, err := e1.CollectorJSON(agentRunID) - if nil != err { - t.Fatal(err) - } - if string(json) != `["12345",{"reservoir_size":2,"events_seen":7},[0.24,0.25]]` { - t.Error(string(json)) - } - if 7 != e1.numSeen { - t.Error(e1.numSeen) - } - if 2 != e1.NumSaved() { - t.Error(e1.NumSaved()) - } -} - -func TestAnalyticsEventMergeFailedSuccess(t *testing.T) { - e1 := newAnalyticsEvents(2) - e2 := newAnalyticsEvents(3) - - e1.addEvent(sampleAnalyticsEvent(0.1)) - e1.addEvent(sampleAnalyticsEvent(0.15)) - e1.addEvent(sampleAnalyticsEvent(0.25)) - - e2.addEvent(sampleAnalyticsEvent(0.06)) - e2.addEvent(sampleAnalyticsEvent(0.12)) - e2.addEvent(sampleAnalyticsEvent(0.18)) - e2.addEvent(sampleAnalyticsEvent(0.24)) - - e1.mergeFailed(e2) - - json, err := e1.CollectorJSON(agentRunID) - if nil != err { - t.Fatal(err) - } - if string(json) != `["12345",{"reservoir_size":2,"events_seen":7},[0.24,0.25]]` { - t.Error(string(json)) - } - if 7 != e1.numSeen { - t.Error(e1.numSeen) - } - if 2 != e1.NumSaved() { - t.Error(e1.NumSaved()) - } - if 1 != e1.failedHarvests { - t.Error(e1.failedHarvests) - } -} - -func TestAnalyticsEventMergeFailedLimitReached(t *testing.T) { - e1 := newAnalyticsEvents(2) - e2 := newAnalyticsEvents(3) - - e1.addEvent(sampleAnalyticsEvent(0.1)) - e1.addEvent(sampleAnalyticsEvent(0.15)) - e1.addEvent(sampleAnalyticsEvent(0.25)) - - e2.addEvent(sampleAnalyticsEvent(0.06)) - e2.addEvent(sampleAnalyticsEvent(0.12)) - e2.addEvent(sampleAnalyticsEvent(0.18)) - e2.addEvent(sampleAnalyticsEvent(0.24)) - - e2.failedHarvests = failedEventsAttemptsLimit - - e1.mergeFailed(e2) - - json, err := e1.CollectorJSON(agentRunID) - if nil != err { - t.Fatal(err) - } - if string(json) != `["12345",{"reservoir_size":2,"events_seen":3},[0.15,0.25]]` { - t.Error(string(json)) - } - if 3 != e1.numSeen { - t.Error(e1.numSeen) - } - if 2 != e1.NumSaved() { - t.Error(e1.NumSaved()) - } - if 0 != e1.failedHarvests { - t.Error(e1.failedHarvests) - } -} - -func analyticsEventBenchmarkHelper(b *testing.B, w jsonWriter) { - events := newAnalyticsEvents(MaxTxnEvents) - event := analyticsEvent{0, w} - for n := 0; n < MaxTxnEvents; n++ { - events.addEvent(event) - } - - b.ReportAllocs() - b.ResetTimer() - - for n := 0; n < b.N; n++ { - js, err := events.CollectorJSON(agentRunID) - if nil != err { - b.Fatal(err, js) - } - } -} - -func BenchmarkTxnEventsCollectorJSON(b *testing.B) { - event := &TxnEvent{ - FinalName: "WebTransaction/Go/zip/zap", - Start: time.Now(), - Duration: 2 * time.Second, - Queuing: 1 * time.Second, - Zone: ApdexSatisfying, - Attrs: nil, - } - analyticsEventBenchmarkHelper(b, event) -} - -func BenchmarkCustomEventsCollectorJSON(b *testing.B) { - now := time.Now() - ce, err := CreateCustomEvent("myEventType", map[string]interface{}{ - "string": "myString", - "bool": true, - "int64": int64(123), - }, now) - if nil != err { - b.Fatal(err) - } - analyticsEventBenchmarkHelper(b, ce) -} - -func BenchmarkErrorEventsCollectorJSON(b *testing.B) { - e := TxnErrorFromResponseCode(time.Now(), 503) - e.Stack = GetStackTrace() - - txnName := "WebTransaction/Go/zip/zap" - event := &ErrorEvent{ - ErrorData: e, - TxnEvent: TxnEvent{ - FinalName: txnName, - Duration: 3 * time.Second, - Attrs: nil, - }, - } - analyticsEventBenchmarkHelper(b, event) -} - -func TestSplitFull(t *testing.T) { - events := newAnalyticsEvents(10) - for i := 0; i < 15; i++ { - events.addEvent(sampleAnalyticsEvent(Priority(float32(i) / 10.0))) - } - // Test that the capacity cannot exceed the max. - if 10 != events.capacity() { - t.Error(events.capacity()) - } - e1, e2 := events.split() - j1, err1 := e1.CollectorJSON(agentRunID) - j2, err2 := e2.CollectorJSON(agentRunID) - if err1 != nil || err2 != nil { - t.Fatal(err1, err2) - } - if string(j1) != `["12345",{"reservoir_size":5,"events_seen":5},[0.5,0.7,0.6,0.8,0.9]]` { - t.Error(string(j1)) - } - if string(j2) != `["12345",{"reservoir_size":5,"events_seen":10},[1.1,1.4,1,1.3,1.2]]` { - t.Error(string(j2)) - } -} - -func TestSplitNotFullOdd(t *testing.T) { - events := newAnalyticsEvents(10) - for i := 0; i < 7; i++ { - events.addEvent(sampleAnalyticsEvent(Priority(float32(i) / 10.0))) - } - e1, e2 := events.split() - j1, err1 := e1.CollectorJSON(agentRunID) - j2, err2 := e2.CollectorJSON(agentRunID) - if err1 != nil || err2 != nil { - t.Fatal(err1, err2) - } - if string(j1) != `["12345",{"reservoir_size":3,"events_seen":3},[0,0.1,0.2]]` { - t.Error(string(j1)) - } - if string(j2) != `["12345",{"reservoir_size":4,"events_seen":4},[0.3,0.4,0.5,0.6]]` { - t.Error(string(j2)) - } -} - -func TestSplitNotFullEven(t *testing.T) { - events := newAnalyticsEvents(10) - for i := 0; i < 8; i++ { - events.addEvent(sampleAnalyticsEvent(Priority(float32(i) / 10.0))) - } - e1, e2 := events.split() - j1, err1 := e1.CollectorJSON(agentRunID) - j2, err2 := e2.CollectorJSON(agentRunID) - if err1 != nil || err2 != nil { - t.Fatal(err1, err2) - } - if string(j1) != `["12345",{"reservoir_size":4,"events_seen":4},[0,0.1,0.2,0.3]]` { - t.Error(string(j1)) - } - if string(j2) != `["12345",{"reservoir_size":4,"events_seen":4},[0.4,0.5,0.6,0.7]]` { - t.Error(string(j2)) - } -} - -func TestAnalyticsEventsZeroCapacity(t *testing.T) { - // Analytics events methods should be safe when configurable harvest - // settings have an event limit of zero. - events := newAnalyticsEvents(0) - if 0 != events.NumSeen() || 0 != events.NumSaved() || 0 != events.capacity() { - t.Error(events.NumSeen(), events.NumSaved(), events.capacity()) - } - events.addEvent(sampleAnalyticsEvent(0.5)) - if 1 != events.NumSeen() || 0 != events.NumSaved() || 0 != events.capacity() { - t.Error(events.NumSeen(), events.NumSaved(), events.capacity()) - } - js, err := events.CollectorJSON("agentRunID") - if err != nil || js != nil { - t.Error(err, string(js)) - } -} diff --git a/internal/apdex.go b/internal/apdex.go deleted file mode 100644 index e4ad6b152..000000000 --- a/internal/apdex.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import "time" - -// ApdexZone is a transaction classification. -type ApdexZone int - -// https://en.wikipedia.org/wiki/Apdex -const ( - ApdexNone ApdexZone = iota - ApdexSatisfying - ApdexTolerating - ApdexFailing -) - -// ApdexFailingThreshold calculates the threshold at which the transaction is -// considered a failure. -func ApdexFailingThreshold(threshold time.Duration) time.Duration { - return 4 * threshold -} - -// CalculateApdexZone calculates the apdex based on the transaction duration and -// threshold. -// -// Note that this does not take into account whether or not the transaction -// had an error. That is expected to be done by the caller. -func CalculateApdexZone(threshold, duration time.Duration) ApdexZone { - if duration <= threshold { - return ApdexSatisfying - } - if duration <= ApdexFailingThreshold(threshold) { - return ApdexTolerating - } - return ApdexFailing -} - -func (zone ApdexZone) label() string { - switch zone { - case ApdexSatisfying: - return "S" - case ApdexTolerating: - return "T" - case ApdexFailing: - return "F" - default: - return "" - } -} diff --git a/internal/apdex_test.go b/internal/apdex_test.go deleted file mode 100644 index 6a03f23ca..000000000 --- a/internal/apdex_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "testing" - "time" -) - -func dur(d int) time.Duration { - return time.Duration(d) -} - -func TestCalculateApdexZone(t *testing.T) { - if z := CalculateApdexZone(dur(10), dur(1)); z != ApdexSatisfying { - t.Fatal(z) - } - if z := CalculateApdexZone(dur(10), dur(10)); z != ApdexSatisfying { - t.Fatal(z) - } - if z := CalculateApdexZone(dur(10), dur(11)); z != ApdexTolerating { - t.Fatal(z) - } - if z := CalculateApdexZone(dur(10), dur(40)); z != ApdexTolerating { - t.Fatal(z) - } - if z := CalculateApdexZone(dur(10), dur(41)); z != ApdexFailing { - t.Fatal(z) - } - if z := CalculateApdexZone(dur(10), dur(100)); z != ApdexFailing { - t.Fatal(z) - } -} - -func TestApdexLabel(t *testing.T) { - if out := ApdexSatisfying.label(); "S" != out { - t.Fatal(out) - } - if out := ApdexTolerating.label(); "T" != out { - t.Fatal(out) - } - if out := ApdexFailing.label(); "F" != out { - t.Fatal(out) - } - if out := ApdexNone.label(); "" != out { - t.Fatal(out) - } -} diff --git a/internal/attributes.go b/internal/attributes.go deleted file mode 100644 index e31fd8d4e..000000000 --- a/internal/attributes.go +++ /dev/null @@ -1,614 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "fmt" - "net/http" - "net/url" - "sort" - "strconv" - "strings" -) - -// AgentAttributeID uniquely identifies each agent attribute. -type AgentAttributeID int - -// New agent attributes must be added in the following places: -// * Constants here. -// * Top level attributes.go file. -// * agentAttributeInfo -const ( - AttributeHostDisplayName AgentAttributeID = iota - attributeRequestMethod - attributeRequestAcceptHeader - attributeRequestContentType - attributeRequestContentLength - attributeRequestHeadersHost - attributeRequestHeadersUserAgent - attributeRequestHeadersReferer - attributeRequestURI - attributeResponseHeadersContentType - attributeResponseHeadersContentLength - attributeResponseCode - AttributeAWSRequestID - AttributeAWSLambdaARN - AttributeAWSLambdaColdStart - AttributeAWSLambdaEventSourceARN - AttributeMessageRoutingKey - AttributeMessageQueueName - AttributeMessageExchangeType - AttributeMessageReplyTo - AttributeMessageCorrelationID -) - -// SpanAttribute is an attribute put in span events. -type SpanAttribute string - -// AddAgentSpanAttributer should be implemented by the Transaction. -type AddAgentSpanAttributer interface { - AddAgentSpanAttribute(key SpanAttribute, val string) -} - -// AddAgentSpanAttribute allows instrumentation packages to add span attributes. -func AddAgentSpanAttribute(txn interface{}, key SpanAttribute, val string) { - if aa, ok := txn.(AddAgentSpanAttributer); ok { - aa.AddAgentSpanAttribute(key, val) - } -} - -// These span event string constants must match the contents of the top level -// attributes.go file. -const ( - spanAttributeDBStatement SpanAttribute = "db.statement" - spanAttributeDBInstance SpanAttribute = "db.instance" - spanAttributeDBCollection SpanAttribute = "db.collection" - spanAttributePeerAddress SpanAttribute = "peer.address" - spanAttributePeerHostname SpanAttribute = "peer.hostname" - spanAttributeHTTPURL SpanAttribute = "http.url" - spanAttributeHTTPMethod SpanAttribute = "http.method" - // query parameters only appear in segments, not span events, but is - // listed as span attributes to simplify code. - spanAttributeQueryParameters SpanAttribute = "query_parameters" - // These span attributes are added by aws sdk instrumentation. - // https://source.datanerd.us/agents/agent-specs/blob/master/implementation_guides/aws-sdk.md#span-and-segment-attributes - SpanAttributeAWSOperation SpanAttribute = "aws.operation" - SpanAttributeAWSRequestID SpanAttribute = "aws.requestId" - SpanAttributeAWSRegion SpanAttribute = "aws.region" -) - -func (sa SpanAttribute) String() string { return string(sa) } - -var ( - usualDests = DestAll &^ destBrowser - tracesDests = destTxnTrace | destError - agentAttributeInfo = map[AgentAttributeID]struct { - name string - defaultDests destinationSet - }{ - AttributeHostDisplayName: {name: "host.displayName", defaultDests: usualDests}, - attributeRequestMethod: {name: "request.method", defaultDests: usualDests}, - attributeRequestAcceptHeader: {name: "request.headers.accept", defaultDests: usualDests}, - attributeRequestContentType: {name: "request.headers.contentType", defaultDests: usualDests}, - attributeRequestContentLength: {name: "request.headers.contentLength", defaultDests: usualDests}, - attributeRequestHeadersHost: {name: "request.headers.host", defaultDests: usualDests}, - attributeRequestHeadersUserAgent: {name: "request.headers.User-Agent", defaultDests: tracesDests}, - attributeRequestHeadersReferer: {name: "request.headers.referer", defaultDests: tracesDests}, - attributeRequestURI: {name: "request.uri", defaultDests: usualDests}, - attributeResponseHeadersContentType: {name: "response.headers.contentType", defaultDests: usualDests}, - attributeResponseHeadersContentLength: {name: "response.headers.contentLength", defaultDests: usualDests}, - attributeResponseCode: {name: "httpResponseCode", defaultDests: usualDests}, - AttributeAWSRequestID: {name: "aws.requestId", defaultDests: usualDests}, - AttributeAWSLambdaARN: {name: "aws.lambda.arn", defaultDests: usualDests}, - AttributeAWSLambdaColdStart: {name: "aws.lambda.coldStart", defaultDests: usualDests}, - AttributeAWSLambdaEventSourceARN: {name: "aws.lambda.eventSource.arn", defaultDests: usualDests}, - AttributeMessageRoutingKey: {name: "message.routingKey", defaultDests: usualDests}, - AttributeMessageQueueName: {name: "message.queueName", defaultDests: usualDests}, - AttributeMessageExchangeType: {name: "message.exchangeType", defaultDests: destNone}, - AttributeMessageReplyTo: {name: "message.replyTo", defaultDests: destNone}, - AttributeMessageCorrelationID: {name: "message.correlationId", defaultDests: destNone}, - } - spanAttributes = []SpanAttribute{ - spanAttributeDBStatement, - spanAttributeDBInstance, - spanAttributeDBCollection, - spanAttributePeerAddress, - spanAttributePeerHostname, - spanAttributeHTTPURL, - spanAttributeHTTPMethod, - spanAttributeQueryParameters, - SpanAttributeAWSOperation, - SpanAttributeAWSRequestID, - SpanAttributeAWSRegion, - } -) - -func (id AgentAttributeID) name() string { return agentAttributeInfo[id].name } - -// https://source.datanerd.us/agents/agent-specs/blob/master/Agent-Attributes-PORTED.md - -// AttributeDestinationConfig matches newrelic.AttributeDestinationConfig to -// avoid circular dependency issues. -type AttributeDestinationConfig struct { - Enabled bool - Include []string - Exclude []string -} - -type destinationSet int - -const ( - destTxnEvent destinationSet = 1 << iota - destError - destTxnTrace - destBrowser - destSpan - destSegment -) - -const ( - destNone destinationSet = 0 - // DestAll contains all destinations. - DestAll destinationSet = destTxnEvent | destTxnTrace | destError | destBrowser | destSpan | destSegment -) - -const ( - attributeWildcardSuffix = '*' -) - -type attributeModifier struct { - match string // This will not contain a trailing '*'. - includeExclude -} - -type byMatch []*attributeModifier - -func (m byMatch) Len() int { return len(m) } -func (m byMatch) Swap(i, j int) { m[i], m[j] = m[j], m[i] } -func (m byMatch) Less(i, j int) bool { return m[i].match < m[j].match } - -// AttributeConfig is created at connect and shared between all transactions. -type AttributeConfig struct { - disabledDestinations destinationSet - exactMatchModifiers map[string]*attributeModifier - // Once attributeConfig is constructed, wildcardModifiers is sorted in - // lexicographical order. Modifiers appearing later have precedence - // over modifiers appearing earlier. - wildcardModifiers []*attributeModifier - agentDests map[AgentAttributeID]destinationSet - spanDests map[SpanAttribute]destinationSet -} - -type includeExclude struct { - include destinationSet - exclude destinationSet -} - -func modifierApply(m *attributeModifier, d destinationSet) destinationSet { - // Include before exclude, since exclude has priority. - d |= m.include - d &^= m.exclude - return d -} - -func applyAttributeConfig(c *AttributeConfig, key string, d destinationSet) destinationSet { - // Important: The wildcard modifiers must be applied before the exact - // match modifiers, and the slice must be iterated in a forward - // direction. - for _, m := range c.wildcardModifiers { - if strings.HasPrefix(key, m.match) { - d = modifierApply(m, d) - } - } - - if m, ok := c.exactMatchModifiers[key]; ok { - d = modifierApply(m, d) - } - - d &^= c.disabledDestinations - - return d -} - -func addModifier(c *AttributeConfig, match string, d includeExclude) { - if "" == match { - return - } - exactMatch := true - if attributeWildcardSuffix == match[len(match)-1] { - exactMatch = false - match = match[0 : len(match)-1] - } - mod := &attributeModifier{ - match: match, - includeExclude: d, - } - - if exactMatch { - if m, ok := c.exactMatchModifiers[mod.match]; ok { - m.include |= mod.include - m.exclude |= mod.exclude - } else { - c.exactMatchModifiers[mod.match] = mod - } - } else { - for _, m := range c.wildcardModifiers { - // Important: Duplicate entries for the same match - // string would not work because exclude needs - // precedence over include. - if m.match == mod.match { - m.include |= mod.include - m.exclude |= mod.exclude - return - } - } - c.wildcardModifiers = append(c.wildcardModifiers, mod) - } -} - -func processDest(c *AttributeConfig, includeEnabled bool, dc *AttributeDestinationConfig, d destinationSet) { - if !dc.Enabled { - c.disabledDestinations |= d - } - if includeEnabled { - for _, match := range dc.Include { - addModifier(c, match, includeExclude{include: d}) - } - } - for _, match := range dc.Exclude { - addModifier(c, match, includeExclude{exclude: d}) - } -} - -// AttributeConfigInput is used as the input to CreateAttributeConfig: it -// transforms newrelic.Config settings into an AttributeConfig. -type AttributeConfigInput struct { - Attributes AttributeDestinationConfig - ErrorCollector AttributeDestinationConfig - TransactionEvents AttributeDestinationConfig - BrowserMonitoring AttributeDestinationConfig - TransactionTracer AttributeDestinationConfig - SpanEvents AttributeDestinationConfig - TraceSegments AttributeDestinationConfig -} - -var ( - sampleAttributeConfigInput = AttributeConfigInput{ - Attributes: AttributeDestinationConfig{Enabled: true}, - ErrorCollector: AttributeDestinationConfig{Enabled: true}, - TransactionEvents: AttributeDestinationConfig{Enabled: true}, - TransactionTracer: AttributeDestinationConfig{Enabled: true}, - BrowserMonitoring: AttributeDestinationConfig{Enabled: true}, - SpanEvents: AttributeDestinationConfig{Enabled: true}, - TraceSegments: AttributeDestinationConfig{Enabled: true}, - } -) - -// CreateAttributeConfig creates a new AttributeConfig. -func CreateAttributeConfig(input AttributeConfigInput, includeEnabled bool) *AttributeConfig { - c := &AttributeConfig{ - exactMatchModifiers: make(map[string]*attributeModifier), - wildcardModifiers: make([]*attributeModifier, 0, 64), - } - - processDest(c, includeEnabled, &input.Attributes, DestAll) - processDest(c, includeEnabled, &input.ErrorCollector, destError) - processDest(c, includeEnabled, &input.TransactionEvents, destTxnEvent) - processDest(c, includeEnabled, &input.TransactionTracer, destTxnTrace) - processDest(c, includeEnabled, &input.BrowserMonitoring, destBrowser) - processDest(c, includeEnabled, &input.SpanEvents, destSpan) - processDest(c, includeEnabled, &input.TraceSegments, destSegment) - - sort.Sort(byMatch(c.wildcardModifiers)) - - c.agentDests = make(map[AgentAttributeID]destinationSet) - for id, info := range agentAttributeInfo { - c.agentDests[id] = applyAttributeConfig(c, info.name, info.defaultDests) - } - c.spanDests = make(map[SpanAttribute]destinationSet, len(spanAttributes)) - for _, id := range spanAttributes { - c.spanDests[id] = applyAttributeConfig(c, id.String(), destSpan|destSegment) - } - - return c -} - -type userAttribute struct { - value interface{} - dests destinationSet -} - -type agentAttributeValue struct { - stringVal string - otherVal interface{} -} - -type agentAttributes map[AgentAttributeID]agentAttributeValue - -func (a *Attributes) filterSpanAttributes(s map[SpanAttribute]jsonWriter, d destinationSet) map[SpanAttribute]jsonWriter { - if nil != a { - for key := range s { - if a.config.spanDests[key]&d == 0 { - delete(s, key) - } - } - } - return s -} - -// GetAgentValue is used to access agent attributes. This function returns ("", -// nil) if the attribute doesn't exist or it doesn't match the destinations -// provided. -func (a *Attributes) GetAgentValue(id AgentAttributeID, d destinationSet) (string, interface{}) { - if nil == a || 0 == a.config.agentDests[id]&d { - return "", nil - } - v, _ := a.Agent[id] - return v.stringVal, v.otherVal -} - -// AddAgentAttributer allows instrumentation to add agent attributes without -// exposing a Transaction method. -type AddAgentAttributer interface { - AddAgentAttribute(id AgentAttributeID, stringVal string, otherVal interface{}) -} - -// Add is used to add agent attributes. Only one of stringVal and -// otherVal should be populated. Since most agent attribute values are strings, -// stringVal exists to avoid allocations. -func (attr agentAttributes) Add(id AgentAttributeID, stringVal string, otherVal interface{}) { - if "" != stringVal || otherVal != nil { - attr[id] = agentAttributeValue{ - stringVal: truncateStringValueIfLong(stringVal), - otherVal: otherVal, - } - } -} - -// Attributes are key value pairs attached to the various collected data types. -type Attributes struct { - config *AttributeConfig - user map[string]userAttribute - Agent agentAttributes -} - -// NewAttributes creates a new Attributes. -func NewAttributes(config *AttributeConfig) *Attributes { - return &Attributes{ - config: config, - Agent: make(agentAttributes), - } -} - -// ErrInvalidAttributeType is returned when the value is not valid. -type ErrInvalidAttributeType struct { - key string - val interface{} -} - -func (e ErrInvalidAttributeType) Error() string { - return fmt.Sprintf("attribute '%s' value of type %T is invalid", e.key, e.val) -} - -type invalidAttributeKeyErr struct{ key string } - -func (e invalidAttributeKeyErr) Error() string { - return fmt.Sprintf("attribute key '%.32s...' exceeds length limit %d", - e.key, attributeKeyLengthLimit) -} - -type userAttributeLimitErr struct{ key string } - -func (e userAttributeLimitErr) Error() string { - return fmt.Sprintf("attribute '%s' discarded: limit of %d reached", e.key, - attributeUserLimit) -} - -func truncateStringValueIfLong(val string) string { - if len(val) > attributeValueLengthLimit { - return StringLengthByteLimit(val, attributeValueLengthLimit) - } - return val -} - -// ValidateUserAttribute validates a user attribute. -func ValidateUserAttribute(key string, val interface{}) (interface{}, error) { - if str, ok := val.(string); ok { - val = interface{}(truncateStringValueIfLong(str)) - } - - switch val.(type) { - case string, bool, - uint8, uint16, uint32, uint64, int8, int16, int32, int64, - float32, float64, uint, int, uintptr: - default: - return nil, ErrInvalidAttributeType{ - key: key, - val: val, - } - } - - // Attributes whose keys are excessively long are dropped rather than - // truncated to avoid worrying about the application of configuration to - // truncated values or performing the truncation after configuration. - if len(key) > attributeKeyLengthLimit { - return nil, invalidAttributeKeyErr{key: key} - } - return val, nil -} - -// AddUserAttribute adds a user attribute. -func AddUserAttribute(a *Attributes, key string, val interface{}, d destinationSet) error { - val, err := ValidateUserAttribute(key, val) - if nil != err { - return err - } - dests := applyAttributeConfig(a.config, key, d) - if destNone == dests { - return nil - } - if nil == a.user { - a.user = make(map[string]userAttribute) - } - - if _, exists := a.user[key]; !exists && len(a.user) >= attributeUserLimit { - return userAttributeLimitErr{key} - } - - // Note: Duplicates are overridden: last attribute in wins. - a.user[key] = userAttribute{ - value: val, - dests: dests, - } - return nil -} - -func writeAttributeValueJSON(w *jsonFieldsWriter, key string, val interface{}) { - switch v := val.(type) { - case string: - w.stringField(key, v) - case bool: - if v { - w.rawField(key, `true`) - } else { - w.rawField(key, `false`) - } - case uint8: - w.intField(key, int64(v)) - case uint16: - w.intField(key, int64(v)) - case uint32: - w.intField(key, int64(v)) - case uint64: - w.intField(key, int64(v)) - case uint: - w.intField(key, int64(v)) - case uintptr: - w.intField(key, int64(v)) - case int8: - w.intField(key, int64(v)) - case int16: - w.intField(key, int64(v)) - case int32: - w.intField(key, int64(v)) - case int64: - w.intField(key, v) - case int: - w.intField(key, int64(v)) - case float32: - w.floatField(key, float64(v)) - case float64: - w.floatField(key, v) - default: - w.stringField(key, fmt.Sprintf("%T", v)) - } -} - -func agentAttributesJSON(a *Attributes, buf *bytes.Buffer, d destinationSet) { - if nil == a { - buf.WriteString("{}") - return - } - w := jsonFieldsWriter{buf: buf} - buf.WriteByte('{') - for id, val := range a.Agent { - if 0 != a.config.agentDests[id]&d { - if val.stringVal != "" { - w.stringField(id.name(), val.stringVal) - } else { - writeAttributeValueJSON(&w, id.name(), val.otherVal) - } - } - } - buf.WriteByte('}') - -} - -func userAttributesJSON(a *Attributes, buf *bytes.Buffer, d destinationSet, extraAttributes map[string]interface{}) { - buf.WriteByte('{') - if nil != a { - w := jsonFieldsWriter{buf: buf} - for key, val := range extraAttributes { - outputDest := applyAttributeConfig(a.config, key, d) - if 0 != outputDest&d { - writeAttributeValueJSON(&w, key, val) - } - } - for name, atr := range a.user { - if 0 != atr.dests&d { - if _, found := extraAttributes[name]; found { - continue - } - writeAttributeValueJSON(&w, name, atr.value) - } - } - } - buf.WriteByte('}') -} - -// userAttributesStringJSON is only used for testing. -func userAttributesStringJSON(a *Attributes, d destinationSet, extraAttributes map[string]interface{}) string { - estimate := len(a.user) * 128 - buf := bytes.NewBuffer(make([]byte, 0, estimate)) - userAttributesJSON(a, buf, d, extraAttributes) - return buf.String() -} - -// RequestAgentAttributes gathers agent attributes out of the request. -func RequestAgentAttributes(a *Attributes, method string, h http.Header, u *url.URL) { - a.Agent.Add(attributeRequestMethod, method, nil) - - if nil != u { - a.Agent.Add(attributeRequestURI, SafeURL(u), nil) - } - - if nil == h { - return - } - a.Agent.Add(attributeRequestAcceptHeader, h.Get("Accept"), nil) - a.Agent.Add(attributeRequestContentType, h.Get("Content-Type"), nil) - a.Agent.Add(attributeRequestHeadersHost, h.Get("Host"), nil) - a.Agent.Add(attributeRequestHeadersUserAgent, h.Get("User-Agent"), nil) - a.Agent.Add(attributeRequestHeadersReferer, SafeURLFromString(h.Get("Referer")), nil) - - if l := GetContentLengthFromHeader(h); l >= 0 { - a.Agent.Add(attributeRequestContentLength, "", l) - } -} - -// ResponseHeaderAttributes gather agent attributes from the response headers. -func ResponseHeaderAttributes(a *Attributes, h http.Header) { - if nil == h { - return - } - a.Agent.Add(attributeResponseHeadersContentType, h.Get("Content-Type"), nil) - - if l := GetContentLengthFromHeader(h); l >= 0 { - a.Agent.Add(attributeResponseHeadersContentLength, "", l) - } -} - -var ( - // statusCodeLookup avoids a strconv.Itoa call. - statusCodeLookup = map[int]string{ - 100: "100", 101: "101", - 200: "200", 201: "201", 202: "202", 203: "203", 204: "204", 205: "205", 206: "206", - 300: "300", 301: "301", 302: "302", 303: "303", 304: "304", 305: "305", 307: "307", - 400: "400", 401: "401", 402: "402", 403: "403", 404: "404", 405: "405", 406: "406", - 407: "407", 408: "408", 409: "409", 410: "410", 411: "411", 412: "412", 413: "413", - 414: "414", 415: "415", 416: "416", 417: "417", 418: "418", 428: "428", 429: "429", - 431: "431", 451: "451", - 500: "500", 501: "501", 502: "502", 503: "503", 504: "504", 505: "505", 511: "511", - } -) - -// ResponseCodeAttribute sets the response code agent attribute. -func ResponseCodeAttribute(a *Attributes, code int) { - rc := statusCodeLookup[code] - if rc == "" { - rc = strconv.Itoa(code) - } - a.Agent.Add(attributeResponseCode, rc, nil) -} diff --git a/internal/attributes_test.go b/internal/attributes_test.go deleted file mode 100644 index bf35801a3..000000000 --- a/internal/attributes_test.go +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "encoding/json" - "net/http" - "strconv" - "strings" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -type AttributeTestcase struct { - Testname string `json:"testname"` - Config struct { - AttributesEnabled bool `json:"attributes.enabled"` - AttributesInclude []string `json:"attributes.include"` - AttributesExclude []string `json:"attributes.exclude"` - BrowserAttributesEnabled bool `json:"browser_monitoring.attributes.enabled"` - BrowserAttributesInclude []string `json:"browser_monitoring.attributes.include"` - BrowserAttributesExclude []string `json:"browser_monitoring.attributes.exclude"` - ErrorAttributesEnabled bool `json:"error_collector.attributes.enabled"` - ErrorAttributesInclude []string `json:"error_collector.attributes.include"` - ErrorAttributesExclude []string `json:"error_collector.attributes.exclude"` - EventsAttributesEnabled bool `json:"transaction_events.attributes.enabled"` - EventsAttributesInclude []string `json:"transaction_events.attributes.include"` - EventsAttributesExclude []string `json:"transaction_events.attributes.exclude"` - TracerAttributesEnabled bool `json:"transaction_tracer.attributes.enabled"` - TracerAttributesInclude []string `json:"transaction_tracer.attributes.include"` - TracerAttributesExclude []string `json:"transaction_tracer.attributes.exclude"` - } `json:"config"` - Key string `json:"input_key"` - InputDestinations []string `json:"input_default_destinations"` - ExpectedDestinations []string `json:"expected_destinations"` -} - -var ( - destTranslate = map[string]destinationSet{ - "attributes": DestAll, - "transaction_events": destTxnEvent, - "transaction_tracer": destTxnTrace, - "error_collector": destError, - "browser_monitoring": destBrowser, - } -) - -func destinationsFromArray(dests []string) destinationSet { - d := destNone - for _, s := range dests { - if x, ok := destTranslate[s]; ok { - d |= x - } - } - return d -} - -func destToString(d destinationSet) string { - if 0 == d { - return "none" - } - out := "" - for _, ds := range []struct { - Name string - Dest destinationSet - }{ - {Name: "event", Dest: destTxnEvent}, - {Name: "trace", Dest: destTxnTrace}, - {Name: "error", Dest: destError}, - {Name: "browser", Dest: destBrowser}, - {Name: "span", Dest: destSpan}, - {Name: "segment", Dest: destSegment}, - } { - if 0 != d&ds.Dest { - if "" == out { - out = ds.Name - } else { - out = out + "," + ds.Name - } - } - } - return out -} - -func runAttributeTestcase(t *testing.T, js json.RawMessage) { - var tc AttributeTestcase - - tc.Config.AttributesEnabled = true - tc.Config.BrowserAttributesEnabled = false - tc.Config.ErrorAttributesEnabled = true - tc.Config.EventsAttributesEnabled = true - tc.Config.TracerAttributesEnabled = true - - if err := json.Unmarshal(js, &tc); nil != err { - t.Error(err) - return - } - - input := AttributeConfigInput{ - Attributes: AttributeDestinationConfig{ - Enabled: tc.Config.AttributesEnabled, - Include: tc.Config.AttributesInclude, - Exclude: tc.Config.AttributesExclude, - }, - ErrorCollector: AttributeDestinationConfig{ - Enabled: tc.Config.ErrorAttributesEnabled, - Include: tc.Config.ErrorAttributesInclude, - Exclude: tc.Config.ErrorAttributesExclude, - }, - TransactionEvents: AttributeDestinationConfig{ - Enabled: tc.Config.EventsAttributesEnabled, - Include: tc.Config.EventsAttributesInclude, - Exclude: tc.Config.EventsAttributesExclude, - }, - BrowserMonitoring: AttributeDestinationConfig{ - Enabled: tc.Config.BrowserAttributesEnabled, - Include: tc.Config.BrowserAttributesInclude, - Exclude: tc.Config.BrowserAttributesExclude, - }, - TransactionTracer: AttributeDestinationConfig{ - Enabled: tc.Config.TracerAttributesEnabled, - Include: tc.Config.TracerAttributesInclude, - Exclude: tc.Config.TracerAttributesExclude, - }, - } - - cfg := CreateAttributeConfig(input, true) - - inputDests := destinationsFromArray(tc.InputDestinations) - expectedDests := destinationsFromArray(tc.ExpectedDestinations) - - out := applyAttributeConfig(cfg, tc.Key, inputDests) - - if out != expectedDests { - t.Errorf(`name="%s" input="%s" expected="%s" got="%s"`, - tc.Testname, - destToString(inputDests), - destToString(expectedDests), - destToString(out)) - } -} - -func TestCrossAgentAttributes(t *testing.T) { - var tcs []json.RawMessage - - err := crossagent.ReadJSON("attribute_configuration.json", &tcs) - if err != nil { - t.Fatal(err) - } - - for _, tc := range tcs { - runAttributeTestcase(t, tc) - } -} - -func TestWriteAttributeValueJSON(t *testing.T) { - buf := &bytes.Buffer{} - w := jsonFieldsWriter{buf: buf} - - buf.WriteByte('{') - writeAttributeValueJSON(&w, "a", `escape\me!`) - writeAttributeValueJSON(&w, "a", true) - writeAttributeValueJSON(&w, "a", false) - writeAttributeValueJSON(&w, "a", uint8(1)) - writeAttributeValueJSON(&w, "a", uint16(2)) - writeAttributeValueJSON(&w, "a", uint32(3)) - writeAttributeValueJSON(&w, "a", uint64(4)) - writeAttributeValueJSON(&w, "a", uint(5)) - writeAttributeValueJSON(&w, "a", uintptr(6)) - writeAttributeValueJSON(&w, "a", int8(-1)) - writeAttributeValueJSON(&w, "a", int16(-2)) - writeAttributeValueJSON(&w, "a", int32(-3)) - writeAttributeValueJSON(&w, "a", int64(-4)) - writeAttributeValueJSON(&w, "a", int(-5)) - writeAttributeValueJSON(&w, "a", float32(1.5)) - writeAttributeValueJSON(&w, "a", float64(4.56)) - buf.WriteByte('}') - - expect := CompactJSONString(`{ - "a":"escape\\me!", - "a":true, - "a":false, - "a":1, - "a":2, - "a":3, - "a":4, - "a":5, - "a":6, - "a":-1, - "a":-2, - "a":-3, - "a":-4, - "a":-5, - "a":1.5, - "a":4.56 - }`) - js := buf.String() - if js != expect { - t.Error(js, expect) - } -} - -func TestValidAttributeTypes(t *testing.T) { - testcases := []struct { - Input interface{} - Valid bool - }{ - // Valid attribute types. - {Input: "string value", Valid: true}, - {Input: true, Valid: true}, - {Input: uint8(0), Valid: true}, - {Input: uint16(0), Valid: true}, - {Input: uint32(0), Valid: true}, - {Input: uint64(0), Valid: true}, - {Input: int8(0), Valid: true}, - {Input: int16(0), Valid: true}, - {Input: int32(0), Valid: true}, - {Input: int64(0), Valid: true}, - {Input: float32(0), Valid: true}, - {Input: float64(0), Valid: true}, - {Input: uint(0), Valid: true}, - {Input: int(0), Valid: true}, - {Input: uintptr(0), Valid: true}, - // Invalid attribute types. - {Input: nil, Valid: false}, - {Input: struct{}{}, Valid: false}, - {Input: &struct{}{}, Valid: false}, - } - - for _, tc := range testcases { - val, err := ValidateUserAttribute("key", tc.Input) - _, invalid := err.(ErrInvalidAttributeType) - if tc.Valid == invalid { - t.Error(tc.Input, tc.Valid, val, err) - } - } -} - -func TestUserAttributeValLength(t *testing.T) { - cfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attrs := NewAttributes(cfg) - - atLimit := strings.Repeat("a", attributeValueLengthLimit) - tooLong := atLimit + "a" - - err := AddUserAttribute(attrs, `escape\me`, tooLong, DestAll) - if err != nil { - t.Error(err) - } - js := userAttributesStringJSON(attrs, DestAll, nil) - if `{"escape\\me":"`+atLimit+`"}` != js { - t.Error(js) - } -} - -func TestUserAttributeKeyLength(t *testing.T) { - cfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attrs := NewAttributes(cfg) - - lengthyKey := strings.Repeat("a", attributeKeyLengthLimit+1) - err := AddUserAttribute(attrs, lengthyKey, 123, DestAll) - if _, ok := err.(invalidAttributeKeyErr); !ok { - t.Error(err) - } - js := userAttributesStringJSON(attrs, DestAll, nil) - if `{}` != js { - t.Error(js) - } -} - -func TestNumUserAttributesLimit(t *testing.T) { - cfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attrs := NewAttributes(cfg) - - for i := 0; i < attributeUserLimit; i++ { - s := strconv.Itoa(i) - err := AddUserAttribute(attrs, s, s, DestAll) - if err != nil { - t.Fatal(err) - } - } - - err := AddUserAttribute(attrs, "cant_add_me", 123, DestAll) - if _, ok := err.(userAttributeLimitErr); !ok { - t.Fatal(err) - } - - js := userAttributesStringJSON(attrs, DestAll, nil) - var out map[string]string - err = json.Unmarshal([]byte(js), &out) - if nil != err { - t.Fatal(err) - } - if len(out) != attributeUserLimit { - t.Error(len(out)) - } - if strings.Contains(js, "cant_add_me") { - t.Fatal(js) - } - - // Now test that replacement works when the limit is reached. - err = AddUserAttribute(attrs, "0", "BEEN_REPLACED", DestAll) - if nil != err { - t.Fatal(err) - } - js = userAttributesStringJSON(attrs, DestAll, nil) - if !strings.Contains(js, "BEEN_REPLACED") { - t.Fatal(js) - } -} - -func TestExtraAttributesIncluded(t *testing.T) { - cfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attrs := NewAttributes(cfg) - - err := AddUserAttribute(attrs, "a", 1, DestAll) - if nil != err { - t.Error(err) - } - js := userAttributesStringJSON(attrs, DestAll, map[string]interface{}{"b": 2}) - if `{"b":2,"a":1}` != js { - t.Error(js) - } -} - -func TestExtraAttributesPrecedence(t *testing.T) { - cfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attrs := NewAttributes(cfg) - - err := AddUserAttribute(attrs, "a", 1, DestAll) - if nil != err { - t.Error(err) - } - js := userAttributesStringJSON(attrs, DestAll, map[string]interface{}{"a": 2}) - if `{"a":2}` != js { - t.Error(js) - } -} - -func TestIncludeDisabled(t *testing.T) { - input := sampleAttributeConfigInput - input.Attributes.Include = append(input.Attributes.Include, "include_me") - cfg := CreateAttributeConfig(input, false) - attrs := NewAttributes(cfg) - - err := AddUserAttribute(attrs, "include_me", 1, destNone) - if nil != err { - t.Error(err) - } - js := userAttributesStringJSON(attrs, DestAll, nil) - if `{}` != js { - t.Error(js) - } -} - -func agentAttributesMap(attrs *Attributes, d destinationSet) map[string]interface{} { - buf := &bytes.Buffer{} - agentAttributesJSON(attrs, buf, d) - var m map[string]interface{} - err := json.Unmarshal(buf.Bytes(), &m) - if err != nil { - panic(err) - } - return m -} - -func TestRequestAgentAttributesEmptyInput(t *testing.T) { - cfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attrs := NewAttributes(cfg) - RequestAgentAttributes(attrs, "", nil, nil) - got := agentAttributesMap(attrs, DestAll) - expectAttributes(t, got, map[string]interface{}{}) -} - -func TestRequestAgentAttributesPresent(t *testing.T) { - req, err := http.NewRequest("GET", "http://www.newrelic.com?remove=me", nil) - if nil != err { - t.Fatal(err) - } - req.Header.Set("Accept", "the-accept") - req.Header.Set("Content-Type", "the-content-type") - req.Header.Set("Host", "the-host") - req.Header.Set("User-Agent", "the-agent") - req.Header.Set("Referer", "http://www.example.com") - req.Header.Set("Content-Length", "123") - - cfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - - attrs := NewAttributes(cfg) - RequestAgentAttributes(attrs, req.Method, req.Header, req.URL) - got := agentAttributesMap(attrs, DestAll) - expectAttributes(t, got, map[string]interface{}{ - "request.headers.contentType": "the-content-type", - "request.headers.host": "the-host", - "request.headers.User-Agent": "the-agent", - "request.headers.referer": "http://www.example.com", - "request.headers.contentLength": 123, - "request.method": "GET", - "request.uri": "http://www.newrelic.com", - "request.headers.accept": "the-accept", - }) -} - -func BenchmarkAgentAttributes(b *testing.B) { - cfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - - req, err := http.NewRequest("GET", "http://www.newrelic.com", nil) - if nil != err { - b.Fatal(err) - } - - req.Header.Set("Accept", "zap") - req.Header.Set("Content-Type", "zap") - req.Header.Set("Host", "zap") - req.Header.Set("User-Agent", "zap") - req.Header.Set("Referer", "http://www.newrelic.com") - req.Header.Set("Content-Length", "123") - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - attrs := NewAttributes(cfg) - RequestAgentAttributes(attrs, req.Method, req.Header, req.URL) - buf := bytes.Buffer{} - agentAttributesJSON(attrs, &buf, destTxnTrace) - } -} - -func TestGetAgentValue(t *testing.T) { - // Test nil safe - var attrs *Attributes - outstr, outother := attrs.GetAgentValue(attributeRequestURI, destTxnTrace) - if outstr != "" || outother != nil { - t.Error(outstr, outother) - } - - c := sampleAttributeConfigInput - c.TransactionTracer.Exclude = []string{"request.uri"} - cfg := CreateAttributeConfig(c, true) - attrs = NewAttributes(cfg) - attrs.Agent.Add(attributeResponseHeadersContentLength, "", 123) - attrs.Agent.Add(attributeRequestMethod, "GET", nil) - attrs.Agent.Add(attributeRequestURI, "/url", nil) // disabled by configuration - - outstr, outother = attrs.GetAgentValue(attributeResponseHeadersContentLength, destTxnTrace) - if outstr != "" || outother != 123 { - t.Error(outstr, outother) - } - outstr, outother = attrs.GetAgentValue(attributeRequestMethod, destTxnTrace) - if outstr != "GET" || outother != nil { - t.Error(outstr, outother) - } - outstr, outother = attrs.GetAgentValue(attributeRequestURI, destTxnTrace) - if outstr != "" || outother != nil { - t.Error(outstr, outother) - } -} diff --git a/internal/browser.go b/internal/browser.go deleted file mode 100644 index 82ffa71e5..000000000 --- a/internal/browser.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import "bytes" - -// BrowserAttributes returns a string with the attributes that are attached to -// the browser destination encoded in the JSON format expected by the Browser -// agent. -func BrowserAttributes(a *Attributes) []byte { - buf := &bytes.Buffer{} - - buf.WriteString(`{"u":`) - userAttributesJSON(a, buf, destBrowser, nil) - buf.WriteString(`,"a":`) - agentAttributesJSON(a, buf, destBrowser) - buf.WriteByte('}') - - return buf.Bytes() -} diff --git a/internal/browser_test.go b/internal/browser_test.go deleted file mode 100644 index 7806755ee..000000000 --- a/internal/browser_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "testing" -) - -func TestBrowserAttributesNil(t *testing.T) { - expected := `{"u":{},"a":{}}` - actual := string(BrowserAttributes(nil)) - if expected != actual { - t.Errorf("unexpected browser attributes: expected %s; got %s", expected, actual) - } -} - -func TestBrowserAttributes(t *testing.T) { - a := NewAttributes(CreateAttributeConfig(sampleAttributeConfigInput, true)) - AddUserAttribute(a, "user", "thing", destBrowser) - AddUserAttribute(a, "not", "shown", destError) - a.Agent.Add(AttributeHostDisplayName, "host", nil) - - expected := `{"u":{"user":"thing"},"a":{}}` - actual := string(BrowserAttributes(a)) - if expected != actual { - t.Errorf("unexpected browser attributes: expected %s; got %s", expected, actual) - } -} diff --git a/internal/cat/appdata.go b/internal/cat/appdata.go deleted file mode 100644 index f0cd3766d..000000000 --- a/internal/cat/appdata.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "bytes" - "encoding/json" - "errors" - - "github.com/newrelic/go-agent/internal/jsonx" -) - -// AppDataHeader represents a decoded AppData header. -type AppDataHeader struct { - CrossProcessID string - TransactionName string - QueueTimeInSeconds float64 - ResponseTimeInSeconds float64 - ContentLength int64 - TransactionGUID string -} - -var ( - errInvalidAppDataJSON = errors.New("invalid transaction data JSON") - errInvalidAppDataCrossProcessID = errors.New("cross process ID is not a string") - errInvalidAppDataTransactionName = errors.New("transaction name is not a string") - errInvalidAppDataQueueTimeInSeconds = errors.New("queue time is not a float64") - errInvalidAppDataResponseTimeInSeconds = errors.New("response time is not a float64") - errInvalidAppDataContentLength = errors.New("content length is not a float64") - errInvalidAppDataTransactionGUID = errors.New("transaction GUID is not a string") -) - -// MarshalJSON marshalls an AppDataHeader as raw JSON. -func (appData *AppDataHeader) MarshalJSON() ([]byte, error) { - buf := bytes.NewBufferString("[") - - jsonx.AppendString(buf, appData.CrossProcessID) - - buf.WriteString(",") - jsonx.AppendString(buf, appData.TransactionName) - - buf.WriteString(",") - jsonx.AppendFloat(buf, appData.QueueTimeInSeconds) - - buf.WriteString(",") - jsonx.AppendFloat(buf, appData.ResponseTimeInSeconds) - - buf.WriteString(",") - jsonx.AppendInt(buf, appData.ContentLength) - - buf.WriteString(",") - jsonx.AppendString(buf, appData.TransactionGUID) - - // The mysterious unused field. We don't need to round trip this, so we'll - // just hardcode it to false. - buf.WriteString(",false]") - return buf.Bytes(), nil -} - -// UnmarshalJSON unmarshalls an AppDataHeader from raw JSON. -func (appData *AppDataHeader) UnmarshalJSON(data []byte) error { - var ok bool - var v interface{} - - if err := json.Unmarshal(data, &v); err != nil { - return err - } - - arr, ok := v.([]interface{}) - if !ok { - return errInvalidAppDataJSON - } - if len(arr) < 7 { - return errUnexpectedArraySize{ - label: "unexpected number of application data elements", - expected: 7, - actual: len(arr), - } - } - - if appData.CrossProcessID, ok = arr[0].(string); !ok { - return errInvalidAppDataCrossProcessID - } - - if appData.TransactionName, ok = arr[1].(string); !ok { - return errInvalidAppDataTransactionName - } - - if appData.QueueTimeInSeconds, ok = arr[2].(float64); !ok { - return errInvalidAppDataQueueTimeInSeconds - } - - if appData.ResponseTimeInSeconds, ok = arr[3].(float64); !ok { - return errInvalidAppDataResponseTimeInSeconds - } - - cl, ok := arr[4].(float64) - if !ok { - return errInvalidAppDataContentLength - } - // Content length is specced as int32, but not all agents are consistent on - // this in practice. Let's handle it as int64 to maximise compatibility. - appData.ContentLength = int64(cl) - - if appData.TransactionGUID, ok = arr[5].(string); !ok { - return errInvalidAppDataTransactionGUID - } - - // As above, we don't bother decoding the unused field here. It just has to - // be present (which was checked earlier with the length check). - - return nil -} diff --git a/internal/cat/appdata_test.go b/internal/cat/appdata_test.go deleted file mode 100644 index 1289e4f1c..000000000 --- a/internal/cat/appdata_test.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "encoding/json" - "testing" -) - -func TestAppDataRoundTrip(t *testing.T) { - for _, test := range []struct { - json string - appData AppDataHeader - }{ - { - json: `["xpid","txn",1,2,4096,"guid",false]`, - appData: AppDataHeader{ - CrossProcessID: "xpid", - TransactionName: "txn", - QueueTimeInSeconds: 1.0, - ResponseTimeInSeconds: 2.0, - ContentLength: 4096, - TransactionGUID: "guid", - }, - }, - } { - // Test unmarshalling. - appData := &AppDataHeader{} - if err := json.Unmarshal([]byte(test.json), appData); err != nil { - t.Errorf("given %s: error expected to be nil; got %v", test.json, err) - } - - if test.appData.CrossProcessID != appData.CrossProcessID { - t.Errorf("given %s: CrossProcessID expected to be %s; got %s", test.json, test.appData.CrossProcessID, appData.CrossProcessID) - } - - if test.appData.TransactionName != appData.TransactionName { - t.Errorf("given %s: TransactionName expected to be %s; got %s", test.json, test.appData.TransactionName, appData.TransactionName) - } - - if test.appData.QueueTimeInSeconds != appData.QueueTimeInSeconds { - t.Errorf("given %s: QueueTimeInSeconds expected to be %f; got %f", test.json, test.appData.QueueTimeInSeconds, appData.QueueTimeInSeconds) - } - - if test.appData.ResponseTimeInSeconds != appData.ResponseTimeInSeconds { - t.Errorf("given %s: ResponseTimeInSeconds expected to be %f; got %f", test.json, test.appData.ResponseTimeInSeconds, appData.ResponseTimeInSeconds) - } - - if test.appData.ContentLength != appData.ContentLength { - t.Errorf("given %s: ContentLength expected to be %d; got %d", test.json, test.appData.ContentLength, appData.ContentLength) - } - - if test.appData.TransactionGUID != appData.TransactionGUID { - t.Errorf("given %s: TransactionGUID expected to be %s; got %s", test.json, test.appData.TransactionGUID, appData.TransactionGUID) - } - - // Test marshalling. - data, err := json.Marshal(&test.appData) - if err != nil { - t.Errorf("given %s: error expected to be nil; got %v", test.json, err) - } - - if string(data) != test.json { - t.Errorf("given %s: unexpected JSON %s", test.json, string(data)) - } - } -} - -func TestAppDataUnmarshal(t *testing.T) { - // Test error cases where we get a generic error from the JSON package. - for _, input := range []string{ - // Basic malformed JSON test: beyond this, we're not going to unit test the - // Go standard library's JSON package. - ``, - } { - appData := &AppDataHeader{} - - if err := json.Unmarshal([]byte(input), appData); err == nil { - t.Errorf("given %s: error expected to be non-nil; got nil", input) - } - } - - // Test error cases where a specific variable is returned. - for _, tc := range []struct { - input string - err error - }{ - // Unexpected JSON types. - {`false`, errInvalidAppDataJSON}, - {`true`, errInvalidAppDataJSON}, - {`1234`, errInvalidAppDataJSON}, - {`{}`, errInvalidAppDataJSON}, - {`""`, errInvalidAppDataJSON}, - - // Invalid data types for each field in turn. - {`[0,"txn",1.0,2.0,4096,"guid",false]`, errInvalidAppDataCrossProcessID}, - {`["xpid",0,1.0,2.0,4096,"guid",false]`, errInvalidAppDataTransactionName}, - {`["xpid","txn","queue",2.0,4096,"guid",false]`, errInvalidAppDataQueueTimeInSeconds}, - {`["xpid","txn",1.0,"response",4096,"guid",false]`, errInvalidAppDataResponseTimeInSeconds}, - {`["xpid","txn",1.0,2.0,"content length","guid",false]`, errInvalidAppDataContentLength}, - {`["xpid","txn",1.0,2.0,4096,0,false]`, errInvalidAppDataTransactionGUID}, - } { - appData := &AppDataHeader{} - - if err := json.Unmarshal([]byte(tc.input), appData); err != tc.err { - t.Errorf("given %s: error expected to be %v; got %v", tc.input, tc.err, err) - } - } - - // Test error cases where the incorrect number of elements was provided. - for _, input := range []string{ - `[]`, - `[1,2,3,4,5,6]`, - } { - appData := &AppDataHeader{} - - err := json.Unmarshal([]byte(input), appData) - if _, ok := err.(errUnexpectedArraySize); !ok { - t.Errorf("given %s: error expected to be errUnexpectedArraySize; got %v", input, err) - } - } -} diff --git a/internal/cat/errors.go b/internal/cat/errors.go deleted file mode 100644 index 2f516ed05..000000000 --- a/internal/cat/errors.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "fmt" -) - -type errUnexpectedArraySize struct { - label string - expected int - actual int -} - -func (e errUnexpectedArraySize) Error() string { - return fmt.Sprintf("%s: expected %d; got %d", e.label, e.expected, e.actual) -} diff --git a/internal/cat/headers.go b/internal/cat/headers.go deleted file mode 100644 index 6ca05cd67..000000000 --- a/internal/cat/headers.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package cat provides functionality related to the wire format of CAT -// headers. -package cat - -// These header names don't match the spec in terms of their casing, but does -// match what Go will give us from http.CanonicalHeaderKey(). Besides, HTTP -// headers are case insensitive anyway. Rejoice! -const ( - NewRelicIDName = "X-Newrelic-Id" - NewRelicTxnName = "X-Newrelic-Transaction" - NewRelicAppDataName = "X-Newrelic-App-Data" - NewRelicSyntheticsName = "X-Newrelic-Synthetics" -) diff --git a/internal/cat/id.go b/internal/cat/id.go deleted file mode 100644 index 766eb3def..000000000 --- a/internal/cat/id.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "errors" - "strconv" - "strings" -) - -// IDHeader represents a decoded cross process ID header (generally encoded as -// a string in the form ACCOUNT#BLOB). -type IDHeader struct { - AccountID int - Blob string -} - -var ( - errInvalidAccountID = errors.New("invalid account ID") -) - -// NewIDHeader parses the given decoded ID header and creates an IDHeader -// representing it. -func NewIDHeader(in []byte) (*IDHeader, error) { - parts := strings.Split(string(in), "#") - if len(parts) != 2 { - return nil, errUnexpectedArraySize{ - label: "unexpected number of ID elements", - expected: 2, - actual: len(parts), - } - } - - account, err := strconv.Atoi(parts[0]) - if err != nil { - return nil, errInvalidAccountID - } - - return &IDHeader{ - AccountID: account, - Blob: parts[1], - }, nil -} diff --git a/internal/cat/id_test.go b/internal/cat/id_test.go deleted file mode 100644 index 190463996..000000000 --- a/internal/cat/id_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "testing" -) - -func TestIDHeaderUnmarshal(t *testing.T) { - // Test error cases where the output is errUnexpectedArraySize. - for _, input := range []string{ - ``, - `1234`, - `1234#5678#90`, - `foo`, - } { - _, err := NewIDHeader([]byte(input)) - if _, ok := err.(errUnexpectedArraySize); !ok { - t.Errorf("given %s: error expected to be errUnexpectedArraySize; got %v", input, err) - } - } - - // Test error cases where the output is errInvalidAccountID. - for _, input := range []string{ - `#1234`, - `foo#bar`, - } { - if _, err := NewIDHeader([]byte(input)); err != errInvalidAccountID { - t.Errorf("given %s: error expected to be %v; got %v", input, errInvalidAccountID, err) - } - } - - // Test success cases. - for _, test := range []struct { - input string - expected IDHeader - }{ - {`1234#`, IDHeader{1234, ""}}, - {`1234#5678`, IDHeader{1234, "5678"}}, - {`1234#blob`, IDHeader{1234, "blob"}}, - {`0#5678`, IDHeader{0, "5678"}}, - } { - id, err := NewIDHeader([]byte(test.input)) - - if err != nil { - t.Errorf("given %s: error expected to be nil; got %v", test.input, err) - } - if test.expected.AccountID != id.AccountID { - t.Errorf("given %s: account ID expected to be %d; got %d", test.input, test.expected.AccountID, id.AccountID) - } - if test.expected.Blob != id.Blob { - t.Errorf("given %s: account ID expected to be %s; got %s", test.input, test.expected.Blob, id.Blob) - } - } -} diff --git a/internal/cat/path_hash.go b/internal/cat/path_hash.go deleted file mode 100644 index edb483a33..000000000 --- a/internal/cat/path_hash.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "crypto/md5" - "encoding/binary" - "fmt" - "regexp" -) - -var pathHashValidator = regexp.MustCompile("^[0-9a-f]{8}$") - -// GeneratePathHash generates a path hash given a referring path hash, -// transaction name, and application name. referringPathHash can be an empty -// string if there was no referring path hash. -func GeneratePathHash(referringPathHash, txnName, appName string) (string, error) { - var rph uint32 - if referringPathHash != "" { - if !pathHashValidator.MatchString(referringPathHash) { - // Per the spec, invalid referring path hashes should be treated as "0". - referringPathHash = "0" - } - - if _, err := fmt.Sscanf(referringPathHash, "%x", &rph); err != nil { - fmt.Println(rph) - return "", err - } - rph = (rph << 1) | (rph >> 31) - } - - hashInput := fmt.Sprintf("%s;%s", appName, txnName) - hash := md5.Sum([]byte(hashInput)) - low32 := binary.BigEndian.Uint32(hash[12:]) - - return fmt.Sprintf("%08x", rph^low32), nil -} diff --git a/internal/cat/path_hash_test.go b/internal/cat/path_hash_test.go deleted file mode 100644 index 1d7bf08e0..000000000 --- a/internal/cat/path_hash_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestGeneratePathHash(t *testing.T) { - var tcs []struct { - Name string - ReferringPathHash string - ApplicationName string - TransactionName string - ExpectedPathHash string - } - - err := crossagent.ReadJSON("cat/path_hashing.json", &tcs) - if err != nil { - t.Fatal(err) - } - - for _, tc := range tcs { - hash, err := GeneratePathHash(tc.ReferringPathHash, tc.TransactionName, tc.ApplicationName) - if err != nil { - t.Errorf("%s: error expected to be nil; got %v", tc.Name, err) - } - if hash != tc.ExpectedPathHash { - t.Errorf("%s: expected %s; got %s", tc.Name, tc.ExpectedPathHash, hash) - } - } -} diff --git a/internal/cat/synthetics.go b/internal/cat/synthetics.go deleted file mode 100644 index b88c15476..000000000 --- a/internal/cat/synthetics.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "encoding/json" - "errors" - "fmt" -) - -// SyntheticsHeader represents a decoded Synthetics header. -type SyntheticsHeader struct { - Version int - AccountID int - ResourceID string - JobID string - MonitorID string -} - -var ( - errInvalidSyntheticsJSON = errors.New("invalid synthetics JSON") - errInvalidSyntheticsVersion = errors.New("version is not a float64") - errInvalidSyntheticsAccountID = errors.New("account ID is not a float64") - errInvalidSyntheticsResourceID = errors.New("synthetics resource ID is not a string") - errInvalidSyntheticsJobID = errors.New("synthetics job ID is not a string") - errInvalidSyntheticsMonitorID = errors.New("synthetics monitor ID is not a string") -) - -type errUnexpectedSyntheticsVersion int - -func (e errUnexpectedSyntheticsVersion) Error() string { - return fmt.Sprintf("unexpected synthetics header version: %d", e) -} - -// UnmarshalJSON unmarshalls a SyntheticsHeader from raw JSON. -func (s *SyntheticsHeader) UnmarshalJSON(data []byte) error { - var ok bool - var v interface{} - - if err := json.Unmarshal(data, &v); err != nil { - return err - } - - arr, ok := v.([]interface{}) - if !ok { - return errInvalidSyntheticsJSON - } - if len(arr) != 5 { - return errUnexpectedArraySize{ - label: "unexpected number of application data elements", - expected: 5, - actual: len(arr), - } - } - - version, ok := arr[0].(float64) - if !ok { - return errInvalidSyntheticsVersion - } - s.Version = int(version) - if s.Version != 1 { - return errUnexpectedSyntheticsVersion(s.Version) - } - - accountID, ok := arr[1].(float64) - if !ok { - return errInvalidSyntheticsAccountID - } - s.AccountID = int(accountID) - - if s.ResourceID, ok = arr[2].(string); !ok { - return errInvalidSyntheticsResourceID - } - - if s.JobID, ok = arr[3].(string); !ok { - return errInvalidSyntheticsJobID - } - - if s.MonitorID, ok = arr[4].(string); !ok { - return errInvalidSyntheticsMonitorID - } - - return nil -} diff --git a/internal/cat/synthetics_test.go b/internal/cat/synthetics_test.go deleted file mode 100644 index 120d1f53c..000000000 --- a/internal/cat/synthetics_test.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "encoding/json" - "testing" -) - -func TestSyntheticsUnmarshalInvalid(t *testing.T) { - // Test error cases where we get a generic error from the JSON package. - for _, input := range []string{ - // Basic malformed JSON test: beyond this, we're not going to unit test the - // Go standard library's JSON package. - ``, - } { - synthetics := &SyntheticsHeader{} - - if err := json.Unmarshal([]byte(input), synthetics); err == nil { - t.Errorf("given %s: error expected to be non-nil; got nil", input) - } - } - - // Test error cases where the incorrect number of elements was provided. - for _, input := range []string{ - `[]`, - `[1,2,3,4]`, - } { - synthetics := &SyntheticsHeader{} - - err := json.Unmarshal([]byte(input), synthetics) - if _, ok := err.(errUnexpectedArraySize); !ok { - t.Errorf("given %s: error expected to be errUnexpectedArraySize; got %v", input, err) - } - } - - // Test error cases with invalid version numbers. - for _, input := range []string{ - `[0,1234,"resource","job","monitor"]`, - `[2,1234,"resource","job","monitor"]`, - } { - synthetics := &SyntheticsHeader{} - - err := json.Unmarshal([]byte(input), synthetics) - if _, ok := err.(errUnexpectedSyntheticsVersion); !ok { - t.Errorf("given %s: error expected to be errUnexpectedSyntheticsVersion; got %v", input, err) - } - } - - // Test error cases where a specific variable is returned. - for _, tc := range []struct { - input string - err error - }{ - // Unexpected JSON types. - {`false`, errInvalidSyntheticsJSON}, - {`true`, errInvalidSyntheticsJSON}, - {`1234`, errInvalidSyntheticsJSON}, - {`{}`, errInvalidSyntheticsJSON}, - {`""`, errInvalidSyntheticsJSON}, - - // Invalid data types for each field in turn. - {`["version",1234,"resource","job","monitor"]`, errInvalidSyntheticsVersion}, - {`[1,"account","resource","job","monitor"]`, errInvalidSyntheticsAccountID}, - {`[1,1234,0,"job","monitor"]`, errInvalidSyntheticsResourceID}, - {`[1,1234,"resource",-1,"monitor"]`, errInvalidSyntheticsJobID}, - {`[1,1234,"resource","job",false]`, errInvalidSyntheticsMonitorID}, - } { - synthetics := &SyntheticsHeader{} - - if err := json.Unmarshal([]byte(tc.input), synthetics); err != tc.err { - t.Errorf("given %s: error expected to be %v; got %v", tc.input, tc.err, err) - } - } -} - -func TestSyntheticsUnmarshalValid(t *testing.T) { - for _, test := range []struct { - json string - synthetics SyntheticsHeader - }{ - { - json: `[1,1234,"resource","job","monitor"]`, - synthetics: SyntheticsHeader{ - Version: 1, - AccountID: 1234, - ResourceID: "resource", - JobID: "job", - MonitorID: "monitor", - }, - }, - } { - // Test unmarshalling. - synthetics := &SyntheticsHeader{} - if err := json.Unmarshal([]byte(test.json), synthetics); err != nil { - t.Errorf("given %s: error expected to be nil; got %v", test.json, err) - } - - if test.synthetics.Version != synthetics.Version { - t.Errorf("given %s: Version expected to be %d; got %d", test.json, test.synthetics.Version, synthetics.Version) - } - - if test.synthetics.AccountID != synthetics.AccountID { - t.Errorf("given %s: AccountID expected to be %d; got %d", test.json, test.synthetics.AccountID, synthetics.AccountID) - } - - if test.synthetics.ResourceID != synthetics.ResourceID { - t.Errorf("given %s: ResourceID expected to be %s; got %s", test.json, test.synthetics.ResourceID, synthetics.ResourceID) - } - - if test.synthetics.JobID != synthetics.JobID { - t.Errorf("given %s: JobID expected to be %s; got %s", test.json, test.synthetics.JobID, synthetics.JobID) - } - - if test.synthetics.MonitorID != synthetics.MonitorID { - t.Errorf("given %s: MonitorID expected to be %s; got %s", test.json, test.synthetics.MonitorID, synthetics.MonitorID) - } - } -} diff --git a/internal/cat/txndata.go b/internal/cat/txndata.go deleted file mode 100644 index 5a589af57..000000000 --- a/internal/cat/txndata.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "bytes" - "encoding/json" - "errors" - - "github.com/newrelic/go-agent/internal/jsonx" -) - -// TxnDataHeader represents a decoded TxnData header. -type TxnDataHeader struct { - GUID string - TripID string - PathHash string -} - -var ( - errInvalidTxnDataJSON = errors.New("invalid transaction data JSON") - errInvalidTxnDataGUID = errors.New("GUID is not a string") - errInvalidTxnDataTripID = errors.New("trip ID is not a string or null") - errInvalidTxnDataPathHash = errors.New("path hash is not a string or null") -) - -// MarshalJSON marshalls a TxnDataHeader as raw JSON. -func (txnData *TxnDataHeader) MarshalJSON() ([]byte, error) { - // Note that, although there are two and four element versions of this header - // in the wild, we will only ever generate the four element version. - - buf := bytes.NewBufferString("[") - - jsonx.AppendString(buf, txnData.GUID) - - // Write the unused second field. - buf.WriteString(",false,") - jsonx.AppendString(buf, txnData.TripID) - - buf.WriteString(",") - jsonx.AppendString(buf, txnData.PathHash) - - buf.WriteString("]") - - return buf.Bytes(), nil -} - -// UnmarshalJSON unmarshalls a TxnDataHeader from raw JSON. -func (txnData *TxnDataHeader) UnmarshalJSON(data []byte) error { - var ok bool - var v interface{} - - if err := json.Unmarshal(data, &v); err != nil { - return err - } - - arr, ok := v.([]interface{}) - if !ok { - return errInvalidTxnDataJSON - } - if len(arr) < 2 { - return errUnexpectedArraySize{ - label: "unexpected number of transaction data elements", - expected: 2, - actual: len(arr), - } - } - - if txnData.GUID, ok = arr[0].(string); !ok { - return errInvalidTxnDataGUID - } - - // Ignore the unused second field. - - // Set up defaults for the optional values. - txnData.TripID = "" - txnData.PathHash = "" - - if len(arr) >= 3 { - // Per the cross agent tests, an explicit null is valid here. - if nil != arr[2] { - if txnData.TripID, ok = arr[2].(string); !ok { - return errInvalidTxnDataTripID - } - } - - if len(arr) >= 4 { - // Per the cross agent tests, an explicit null is also valid here. - if nil != arr[3] { - if txnData.PathHash, ok = arr[3].(string); !ok { - return errInvalidTxnDataPathHash - } - } - } - } - - return nil -} diff --git a/internal/cat/txndata_test.go b/internal/cat/txndata_test.go deleted file mode 100644 index 4bb01fdfb..000000000 --- a/internal/cat/txndata_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package cat - -import ( - "encoding/json" - "testing" -) - -func TestTxnDataRoundTrip(t *testing.T) { - for _, test := range []struct { - input string - output string - txnData TxnDataHeader - }{ - { - input: `["guid",false]`, - output: `["guid",false,"",""]`, - txnData: TxnDataHeader{ - GUID: "guid", - TripID: "", - PathHash: "", - }, - }, - { - input: `["guid",false,"trip"]`, - output: `["guid",false,"trip",""]`, - txnData: TxnDataHeader{ - GUID: "guid", - TripID: "trip", - PathHash: "", - }, - }, - { - input: `["guid",false,null]`, - output: `["guid",false,"",""]`, - txnData: TxnDataHeader{ - GUID: "guid", - TripID: "", - PathHash: "", - }, - }, - { - input: `["guid",false,"trip",null]`, - output: `["guid",false,"trip",""]`, - txnData: TxnDataHeader{ - GUID: "guid", - TripID: "trip", - PathHash: "", - }, - }, - { - input: `["guid",false,"trip","hash"]`, - output: `["guid",false,"trip","hash"]`, - txnData: TxnDataHeader{ - GUID: "guid", - TripID: "trip", - PathHash: "hash", - }, - }, - } { - // Test unmarshalling. - txnData := &TxnDataHeader{} - if err := json.Unmarshal([]byte(test.input), txnData); err != nil { - t.Errorf("given %s: error expected to be nil; got %v", test.input, err) - } - - if test.txnData.GUID != txnData.GUID { - t.Errorf("given %s: GUID expected to be %s; got %s", test.input, test.txnData.GUID, txnData.GUID) - } - - if test.txnData.TripID != txnData.TripID { - t.Errorf("given %s: TripID expected to be %s; got %s", test.input, test.txnData.TripID, txnData.TripID) - } - - if test.txnData.PathHash != txnData.PathHash { - t.Errorf("given %s: PathHash expected to be %s; got %s", test.input, test.txnData.PathHash, txnData.PathHash) - } - - // Test marshalling. - data, err := json.Marshal(&test.txnData) - if err != nil { - t.Errorf("given %s: error expected to be nil; got %v", test.output, err) - } - - if string(data) != test.output { - t.Errorf("given %s: unexpected JSON %s", test.output, string(data)) - } - } -} - -func TestTxnDataUnmarshal(t *testing.T) { - // Test error cases where we get a generic error from the JSON package. - for _, input := range []string{ - // Basic malformed JSON test: beyond this, we're not going to unit test the - // Go standard library's JSON package. - ``, - } { - txnData := &TxnDataHeader{} - - if err := json.Unmarshal([]byte(input), txnData); err == nil { - t.Errorf("given %s: error expected to be non-nil; got nil", input) - } - } - - // Test error cases where the incorrect number of elements was provided. - for _, input := range []string{ - `[]`, - `[1]`, - } { - txnData := &TxnDataHeader{} - - err := json.Unmarshal([]byte(input), txnData) - if _, ok := err.(errUnexpectedArraySize); !ok { - t.Errorf("given %s: error expected to be errUnexpectedArraySize; got %v", input, err) - } - } - - // Test error cases where a specific variable is returned. - for _, tc := range []struct { - input string - err error - }{ - // Unexpected JSON types. - {`false`, errInvalidTxnDataJSON}, - {`true`, errInvalidTxnDataJSON}, - {`1234`, errInvalidTxnDataJSON}, - {`{}`, errInvalidTxnDataJSON}, - {`""`, errInvalidTxnDataJSON}, - - // Invalid data types for each field in turn. - {`[false,false,"trip","hash"]`, errInvalidTxnDataGUID}, - {`["guid",false,0,"hash"]`, errInvalidTxnDataTripID}, - {`["guid",false,"trip",[]]`, errInvalidTxnDataPathHash}, - } { - txnData := &TxnDataHeader{} - - if err := json.Unmarshal([]byte(tc.input), txnData); err != tc.err { - t.Errorf("given %s: error expected to be %v; got %v", tc.input, tc.err, err) - } - } -} diff --git a/internal/cat_test.go b/internal/cat_test.go deleted file mode 100644 index a3960c47d..000000000 --- a/internal/cat_test.go +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -type eventAttributes map[string]interface{} - -func (e eventAttributes) has(key string) bool { - _, ok := e[key] - return ok -} - -func (e eventAttributes) isString(key string, expected string) error { - actual, ok := e[key].(string) - if !ok { - return fmt.Errorf("key %s is not a string; got type %t with value %v", key, e[key], e[key]) - } - - if actual != expected { - return fmt.Errorf("key %s has unexpected value: expected=%s; got=%s", key, expected, actual) - } - - return nil -} - -type harvestedTxnEvent struct { - intrinsics eventAttributes - userAttributes eventAttributes - agentAttributes eventAttributes -} - -func (h *harvestedTxnEvent) UnmarshalJSON(data []byte) error { - var arr []eventAttributes - - if err := json.Unmarshal(data, &arr); err != nil { - return err - } - - if len(arr) != 3 { - return fmt.Errorf("unexpected number of transaction event items: %d", len(arr)) - } - - h.intrinsics = arr[0] - h.userAttributes = arr[1] - h.agentAttributes = arr[2] - - return nil -} - -func harvestTxnDataEvent(t *TxnData) (*harvestedTxnEvent, error) { - // Since transaction event JSON is built using string manipulation, we have - // to do an awkward marshal/unmarshal shuffle to be able to verify the - // intrinsics. - js, err := json.Marshal(&t.TxnEvent) - if err != nil { - return nil, err - } - - event := &harvestedTxnEvent{} - if err := json.Unmarshal(js, event); err != nil { - return nil, err - } - - return event, nil -} - -// This function implements as close as we can get to the round trip tests in -// the cross agent tests. -func TestCatMap(t *testing.T) { - var testcases []struct { - Name string `json:"name"` - AppName string `json:"appName"` - TransactionName string `json:"transactionName"` - TransactionGUID string `json:"transactionGuid"` - InboundPayload []interface{} `json:"inboundPayload"` - ExpectedIntrinsicFields map[string]string `json:"expectedIntrinsicFields"` - NonExpectedIntrinsicFields []string `json:"nonExpectedIntrinsicFields"` - OutboundRequests []struct { - OutboundTxnName string `json:"outboundTxnName"` - ExpectedOutboundPayload json.RawMessage `json:"expectedOutboundPayload"` - } `json:"outboundRequests"` - } - - err := crossagent.ReadJSON("cat/cat_map.json", &testcases) - if err != nil { - t.Fatal(err) - } - - for _, tc := range testcases { - // Fake enough transaction data to run the test. - tr := &TxnData{ - Name: tc.TransactionName, - } - - tr.CrossProcess.Init(true, false, &ConnectReply{ - CrossProcessID: "1#1", - EncodingKey: "foo", - TrustedAccounts: map[int]struct{}{1: {}}, - }) - - // Marshal the inbound payload into JSON for easier testing. - txnData, err := json.Marshal(tc.InboundPayload) - if err != nil { - t.Errorf("%s: error marshalling inbound payload: %v", tc.Name, err) - } - - // Set up the GUID. - if tc.TransactionGUID != "" { - tr.CrossProcess.GUID = tc.TransactionGUID - } - - // Swallow errors, since some of these tests are testing the behaviour when - // erroneous headers are provided. - tr.CrossProcess.handleInboundRequestTxnData(txnData) - - // Simulate outbound requests. - for _, req := range tc.OutboundRequests { - metadata, err := tr.CrossProcess.CreateCrossProcessMetadata(req.OutboundTxnName, tc.AppName) - if err != nil { - t.Errorf("%s: error creating outbound request headers: %v", tc.Name, err) - } - - // Grab and deobfuscate the txndata that would have been sent to the - // external service. - txnData, err := Deobfuscate(metadata.TxnData, tr.CrossProcess.EncodingKey) - if err != nil { - t.Errorf("%s: error deobfuscating outbound request header: %v", tc.Name, err) - } - - // Check the JSON against the expected value. - compacted := CompactJSONString(string(txnData)) - expected := CompactJSONString(string(req.ExpectedOutboundPayload)) - if compacted != expected { - t.Errorf("%s: outbound metadata does not match expected value: expected=%s; got=%s", tc.Name, expected, compacted) - } - } - - // Finalise the transaction, ignoring errors. - tr.CrossProcess.Finalise(tc.TransactionName, tc.AppName) - - // Harvest the event. - event, err := harvestTxnDataEvent(tr) - if err != nil { - t.Errorf("%s: error harvesting event data: %v", tc.Name, err) - } - - // Now we have the event, let's look for the expected intrinsics. - for key, value := range tc.ExpectedIntrinsicFields { - // First, check if the key exists at all. - if !event.intrinsics.has(key) { - t.Fatalf("%s: missing intrinsic %s", tc.Name, key) - } - - // Everything we're looking for is a string, so we can be a little lazy - // here. - if err := event.intrinsics.isString(key, value); err != nil { - t.Errorf("%s: %v", tc.Name, err) - } - } - - // Finally, we verify that the unexpected intrinsics didn't miraculously - // appear. - for _, key := range tc.NonExpectedIntrinsicFields { - if event.intrinsics.has(key) { - t.Errorf("%s: expected intrinsic %s to be missing; instead, got value %v", tc.Name, key, event.intrinsics[key]) - } - } - } -} diff --git a/internal/collector.go b/internal/collector.go deleted file mode 100644 index 565d071a3..000000000 --- a/internal/collector.go +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "os" - "regexp" - "strconv" - "sync" - "time" - - "github.com/newrelic/go-agent/internal/logger" -) - -const ( - // ProcotolVersion is the protocol version used to communicate with NR - // backend. - ProcotolVersion = 17 - userAgentPrefix = "NewRelic-Go-Agent/" - - // Methods used in collector communication. - cmdPreconnect = "preconnect" - cmdConnect = "connect" - cmdMetrics = "metric_data" - cmdCustomEvents = "custom_event_data" - cmdTxnEvents = "analytic_event_data" - cmdErrorEvents = "error_event_data" - cmdErrorData = "error_data" - cmdTxnTraces = "transaction_sample_data" - cmdSlowSQLs = "sql_trace_data" - cmdSpanEvents = "span_event_data" -) - -// RpmCmd contains fields specific to an individual call made to RPM. -type RpmCmd struct { - Name string - Collector string - RunID string - Data []byte - RequestHeadersMap map[string]string - MaxPayloadSize int -} - -// RpmControls contains fields which will be the same for all calls made -// by the same application. -type RpmControls struct { - License string - Client *http.Client - Logger logger.Logger - AgentVersion string - GzipWriterPool *sync.Pool -} - -// RPMResponse contains a NR endpoint response. -// -// Agent Behavior Summary: -// -// on connect/preconnect: -// 410 means shutdown -// 200, 202 mean success (start run) -// all other response codes and errors mean try after backoff -// -// on harvest: -// 410 means shutdown -// 401, 409 mean restart run -// 408, 429, 500, 503 mean save data for next harvest -// all other response codes and errors discard the data and continue the current harvest -type RPMResponse struct { - statusCode int - body []byte - // Err indicates whether or not the call was successful: newRPMResponse - // should be used to avoid mismatch between statusCode and Err. - Err error - disconnectSecurityPolicy bool -} - -func newRPMResponse(statusCode int) RPMResponse { - var err error - if statusCode != 200 && statusCode != 202 { - err = fmt.Errorf("response code: %d", statusCode) - } - return RPMResponse{statusCode: statusCode, Err: err} -} - -// IsDisconnect indicates that the agent should disconnect. -func (resp RPMResponse) IsDisconnect() bool { - return resp.statusCode == 410 || resp.disconnectSecurityPolicy -} - -// IsRestartException indicates that the agent should restart. -func (resp RPMResponse) IsRestartException() bool { - return resp.statusCode == 401 || - resp.statusCode == 409 -} - -// ShouldSaveHarvestData indicates that the agent should save the data and try -// to send it in the next harvest. -func (resp RPMResponse) ShouldSaveHarvestData() bool { - switch resp.statusCode { - case 408, 429, 500, 503: - return true - default: - return false - } -} - -func rpmURL(cmd RpmCmd, cs RpmControls) string { - var u url.URL - - u.Host = cmd.Collector - u.Path = "agent_listener/invoke_raw_method" - u.Scheme = "https" - - query := url.Values{} - query.Set("marshal_format", "json") - query.Set("protocol_version", strconv.Itoa(ProcotolVersion)) - query.Set("method", cmd.Name) - query.Set("license_key", cs.License) - - if len(cmd.RunID) > 0 { - query.Set("run_id", cmd.RunID) - } - - u.RawQuery = query.Encode() - return u.String() -} - -func collectorRequestInternal(url string, cmd RpmCmd, cs RpmControls) RPMResponse { - compressed, err := compress(cmd.Data, cs.GzipWriterPool) - if nil != err { - return RPMResponse{Err: err} - } - - if l := compressed.Len(); l > cmd.MaxPayloadSize { - return RPMResponse{Err: fmt.Errorf("Payload size for %s too large: %d greater than %d", cmd.Name, l, cmd.MaxPayloadSize)} - } - - req, err := http.NewRequest("POST", url, compressed) - if nil != err { - return RPMResponse{Err: err} - } - - req.Header.Add("Accept-Encoding", "identity, deflate") - req.Header.Add("Content-Type", "application/octet-stream") - req.Header.Add("User-Agent", userAgentPrefix+cs.AgentVersion) - req.Header.Add("Content-Encoding", "gzip") - for k, v := range cmd.RequestHeadersMap { - req.Header.Add(k, v) - } - - resp, err := cs.Client.Do(req) - if err != nil { - return RPMResponse{Err: err} - } - - defer resp.Body.Close() - - r := newRPMResponse(resp.StatusCode) - - // Read the entire response, rather than using resp.Body as input to json.NewDecoder to - // avoid the issue described here: - // https://github.com/google/go-github/pull/317 - // https://ahmetalpbalkan.com/blog/golang-json-decoder-pitfalls/ - // Also, collector JSON responses are expected to be quite small. - body, err := ioutil.ReadAll(resp.Body) - if nil == r.Err { - r.Err = err - } - r.body = body - - return r -} - -// CollectorRequest makes a request to New Relic. -func CollectorRequest(cmd RpmCmd, cs RpmControls) RPMResponse { - url := rpmURL(cmd, cs) - - if cs.Logger.DebugEnabled() { - cs.Logger.Debug("rpm request", map[string]interface{}{ - "command": cmd.Name, - "url": url, - "payload": JSONString(cmd.Data), - }) - } - - resp := collectorRequestInternal(url, cmd, cs) - - if cs.Logger.DebugEnabled() { - if err := resp.Err; err != nil { - cs.Logger.Debug("rpm failure", map[string]interface{}{ - "command": cmd.Name, - "url": url, - "response": string(resp.body), // Body might not be JSON on failure. - "error": err.Error(), - }) - } else { - cs.Logger.Debug("rpm response", map[string]interface{}{ - "command": cmd.Name, - "url": url, - "response": JSONString(resp.body), - }) - } - } - - return resp -} - -const ( - // NEW_RELIC_HOST can be used to override the New Relic endpoint. This - // is useful for testing. - envHost = "NEW_RELIC_HOST" -) - -var ( - preconnectHostOverride = os.Getenv(envHost) - preconnectHostDefault = "collector.newrelic.com" - preconnectRegionLicenseRegex = regexp.MustCompile(`(^.+?)x`) -) - -func calculatePreconnectHost(license, overrideHost string) string { - if "" != overrideHost { - return overrideHost - } - m := preconnectRegionLicenseRegex.FindStringSubmatch(license) - if len(m) > 1 { - return "collector." + m[1] + ".nr-data.net" - } - return preconnectHostDefault -} - -// ConnectJSONCreator allows the creation of the connect payload JSON to be -// deferred until the SecurityPolicies are acquired and vetted. -type ConnectJSONCreator interface { - CreateConnectJSON(*SecurityPolicies) ([]byte, error) -} - -type preconnectRequest struct { - SecurityPoliciesToken string `json:"security_policies_token,omitempty"` - HighSecurity bool `json:"high_security"` -} - -var ( - errMissingAgentRunID = errors.New("connect reply missing agent run id") -) - -// ConnectAttempt tries to connect an application. -func ConnectAttempt(config ConnectJSONCreator, securityPoliciesToken string, highSecurity bool, cs RpmControls) (*ConnectReply, RPMResponse) { - preconnectData, err := json.Marshal([]preconnectRequest{{ - SecurityPoliciesToken: securityPoliciesToken, - HighSecurity: highSecurity, - }}) - if nil != err { - return nil, RPMResponse{Err: fmt.Errorf("unable to marshal preconnect data: %v", err)} - } - - call := RpmCmd{ - Name: cmdPreconnect, - Collector: calculatePreconnectHost(cs.License, preconnectHostOverride), - Data: preconnectData, - MaxPayloadSize: maxPayloadSizeInBytes, - } - - resp := CollectorRequest(call, cs) - if nil != resp.Err { - return nil, resp - } - - var preconnect struct { - Preconnect PreconnectReply `json:"return_value"` - } - err = json.Unmarshal(resp.body, &preconnect) - if nil != err { - // Certain security policy errors must be treated as a disconnect. - return nil, RPMResponse{ - Err: fmt.Errorf("unable to process preconnect reply: %v", err), - disconnectSecurityPolicy: isDisconnectSecurityPolicyError(err), - } - } - - js, err := config.CreateConnectJSON(preconnect.Preconnect.SecurityPolicies.PointerIfPopulated()) - if nil != err { - return nil, RPMResponse{Err: fmt.Errorf("unable to create connect data: %v", err)} - } - - call.Collector = preconnect.Preconnect.Collector - call.Data = js - call.Name = cmdConnect - - resp = CollectorRequest(call, cs) - if nil != resp.Err { - return nil, resp - } - - reply, err := ConstructConnectReply(resp.body, preconnect.Preconnect) - if nil != err { - return nil, RPMResponse{Err: err} - } - - // Note: This should never happen. It would mean the collector - // response is malformed. This exists merely as extra defensiveness. - if "" == reply.RunID { - return nil, RPMResponse{Err: errMissingAgentRunID} - } - - return reply, resp -} - -// ConstructConnectReply takes the body of a Connect reply, in the form of bytes, and a -// PreconnectReply, and converts it into a *ConnectReply -func ConstructConnectReply(body []byte, preconnect PreconnectReply) (*ConnectReply, error) { - var reply struct { - Reply *ConnectReply `json:"return_value"` - } - reply.Reply = ConnectReplyDefaults() - err := json.Unmarshal(body, &reply) - if nil != err { - return nil, fmt.Errorf("unable to parse connect reply: %v", err) - } - - reply.Reply.PreconnectReply = preconnect - - reply.Reply.AdaptiveSampler = NewAdaptiveSampler( - time.Duration(reply.Reply.SamplingTargetPeriodInSeconds)*time.Second, - reply.Reply.SamplingTarget, - time.Now()) - reply.Reply.rulesCache = newRulesCache(txnNameCacheLimit) - - return reply.Reply, nil -} diff --git a/internal/collector_test.go b/internal/collector_test.go deleted file mode 100644 index d9f4a2daa..000000000 --- a/internal/collector_test.go +++ /dev/null @@ -1,595 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "compress/gzip" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "strings" - "sync" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" - "github.com/newrelic/go-agent/internal/logger" -) - -func TestResponseCodeError(t *testing.T) { - testcases := []struct { - code int - success bool - disconnect bool - restart bool - saveHarvestData bool - }{ - // success - {code: 200, success: true, disconnect: false, restart: false, saveHarvestData: false}, - {code: 202, success: true, disconnect: false, restart: false, saveHarvestData: false}, - // disconnect - {code: 410, success: false, disconnect: true, restart: false, saveHarvestData: false}, - // restart - {code: 401, success: false, disconnect: false, restart: true, saveHarvestData: false}, - {code: 409, success: false, disconnect: false, restart: true, saveHarvestData: false}, - // save data - {code: 408, success: false, disconnect: false, restart: false, saveHarvestData: true}, - {code: 429, success: false, disconnect: false, restart: false, saveHarvestData: true}, - {code: 500, success: false, disconnect: false, restart: false, saveHarvestData: true}, - {code: 503, success: false, disconnect: false, restart: false, saveHarvestData: true}, - // other errors - {code: 400, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 403, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 404, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 405, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 407, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 411, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 413, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 414, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 415, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 417, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 431, success: false, disconnect: false, restart: false, saveHarvestData: false}, - // unexpected weird codes - {code: -1, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 1, success: false, disconnect: false, restart: false, saveHarvestData: false}, - {code: 999999, success: false, disconnect: false, restart: false, saveHarvestData: false}, - } - for _, tc := range testcases { - resp := newRPMResponse(tc.code) - if tc.success != (nil == resp.Err) { - t.Error("error", tc.code, tc.success, resp.Err) - } - if tc.disconnect != resp.IsDisconnect() { - t.Error("disconnect", tc.code, tc.disconnect, resp.Err) - } - if tc.restart != resp.IsRestartException() { - t.Error("restart", tc.code, tc.restart, resp.Err) - } - if tc.saveHarvestData != resp.ShouldSaveHarvestData() { - t.Error("save harvest data", tc.code, tc.saveHarvestData, resp.Err) - } - } -} - -type roundTripperFunc func(*http.Request) (*http.Response, error) - -func (fn roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { - return fn(r) -} - -func TestCollectorRequest(t *testing.T) { - cmd := RpmCmd{ - Name: "cmd_name", - Collector: "collector.com", - RunID: "run_id", - Data: nil, - RequestHeadersMap: map[string]string{"zip": "zap"}, - MaxPayloadSize: maxPayloadSizeInBytes, - } - testField := func(name, v1, v2 string) { - if v1 != v2 { - t.Error(name, v1, v2) - } - } - cs := RpmControls{ - License: "the_license", - Client: &http.Client{ - Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) { - testField("method", r.Method, "POST") - testField("url", r.URL.String(), "https://collector.com/agent_listener/invoke_raw_method?license_key=the_license&marshal_format=json&method=cmd_name&protocol_version=17&run_id=run_id") - testField("Accept-Encoding", r.Header.Get("Accept-Encoding"), "identity, deflate") - testField("Content-Type", r.Header.Get("Content-Type"), "application/octet-stream") - testField("User-Agent", r.Header.Get("User-Agent"), "NewRelic-Go-Agent/agent_version") - testField("Content-Encoding", r.Header.Get("Content-Encoding"), "gzip") - testField("zip", r.Header.Get("zip"), "zap") - return &http.Response{ - StatusCode: 200, - Body: ioutil.NopCloser(strings.NewReader("body")), - }, nil - }), - }, - Logger: logger.ShimLogger{IsDebugEnabled: true}, - AgentVersion: "agent_version", - GzipWriterPool: &sync.Pool{ - New: func() interface{} { - return gzip.NewWriter(io.Discard) - }, - }, - } - resp := CollectorRequest(cmd, cs) - if nil != resp.Err { - t.Error(resp.Err) - } -} - -func TestCollectorBadRequest(t *testing.T) { - cmd := RpmCmd{ - Name: "cmd_name", - Collector: "collector.com", - RunID: "run_id", - Data: nil, - RequestHeadersMap: map[string]string{"zip": "zap"}, - } - cs := RpmControls{ - License: "the_license", - Client: &http.Client{ - Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: 200, - Body: ioutil.NopCloser(strings.NewReader("body")), - }, nil - }), - }, - Logger: logger.ShimLogger{IsDebugEnabled: true}, - AgentVersion: "agent_version", - GzipWriterPool: &sync.Pool{ - New: func() interface{} { - return gzip.NewWriter(io.Discard) - }, - }, - } - u := ":" // bad url - resp := collectorRequestInternal(u, cmd, cs) - if nil == resp.Err { - t.Error("missing expected error") - } - -} - -func TestUrl(t *testing.T) { - cmd := RpmCmd{ - Name: "foo_method", - Collector: "example.com", - } - cs := RpmControls{ - License: "123abc", - Client: nil, - Logger: nil, - AgentVersion: "1", - GzipWriterPool: &sync.Pool{ - New: func() interface{} { - return gzip.NewWriter(io.Discard) - }, - }, - } - - out := rpmURL(cmd, cs) - u, err := url.Parse(out) - if err != nil { - t.Fatalf("url.Parse(%q) = %q", out, err) - } - - got := u.Query().Get("license_key") - if got != cs.License { - t.Errorf("got=%q cmd.License=%q", got, cs.License) - } - if u.Scheme != "https" { - t.Error(u.Scheme) - } -} - -const ( - unknownRequiredPolicyBody = `{"return_value":{"redirect_host":"special_collector","security_policies":{"unknown_policy":{"enabled":true,"required":true}}}}` - redirectBody = `{"return_value":{"redirect_host":"special_collector"}}` - connectBody = `{"return_value":{"agent_run_id":"my_agent_run_id"}}` - malformedBody = `{"return_value":}}` -) - -func makeResponse(code int, body string) *http.Response { - return &http.Response{ - StatusCode: code, - Body: ioutil.NopCloser(strings.NewReader(body)), - } -} - -type endpointResult struct { - response *http.Response - err error -} - -type connectMock struct { - redirect endpointResult - connect endpointResult - // testConfig will be used if this is nil - config ConnectJSONCreator -} - -func (m connectMock) RoundTrip(r *http.Request) (*http.Response, error) { - cmd := r.URL.Query().Get("method") - switch cmd { - case cmdPreconnect: - return m.redirect.response, m.redirect.err - case cmdConnect: - return m.connect.response, m.connect.err - default: - return nil, fmt.Errorf("unknown cmd: %s", cmd) - } -} - -func (m connectMock) CancelRequest(req *http.Request) {} - -type testConfig struct{} - -func (tc testConfig) CreateConnectJSON(*SecurityPolicies) ([]byte, error) { - return []byte(`"connect-json"`), nil -} - -type errorConfig struct{} - -func (c errorConfig) CreateConnectJSON(*SecurityPolicies) ([]byte, error) { - return nil, errors.New("error creating config JSON") -} - -func testConnectHelper(cm connectMock) (*ConnectReply, RPMResponse) { - config := cm.config - if nil == config { - config = testConfig{} - } - cs := RpmControls{ - License: "12345", - Client: &http.Client{Transport: cm}, - Logger: logger.ShimLogger{IsDebugEnabled: true}, - AgentVersion: "1", - GzipWriterPool: &sync.Pool{ - New: func() interface{} { - return gzip.NewWriter(io.Discard) - }, - }, - } - - return ConnectAttempt(config, "", false, cs) -} - -func TestConnectAttemptSuccess(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, redirectBody)}, - connect: endpointResult{response: makeResponse(200, connectBody)}, - }) - if nil == run || nil != resp.Err { - t.Fatal(run, resp.Err) - } - if run.Collector != "special_collector" { - t.Error(run.Collector) - } - if run.RunID != "my_agent_run_id" { - t.Error(run) - } -} - -func TestConnectClientError(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, redirectBody)}, - connect: endpointResult{err: errors.New("client error")}, - }) - if nil != run { - t.Fatal(run) - } - if resp.Err == nil { - t.Fatal("missing expected error") - } -} - -func TestConnectConfigJSONError(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, redirectBody)}, - connect: endpointResult{response: makeResponse(200, connectBody)}, - config: errorConfig{}, - }) - if nil != run { - t.Fatal(run) - } - if resp.Err == nil { - t.Fatal("missing expected error") - } -} - -func TestConnectAttemptDisconnectOnRedirect(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(410, "")}, - connect: endpointResult{response: makeResponse(200, connectBody)}, - }) - if nil != run { - t.Error(run) - } - if nil == resp.Err { - t.Fatal("missing error") - } - if !resp.IsDisconnect() { - t.Fatal("should be disconnect") - } -} - -func TestConnectAttemptDisconnectOnConnect(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, redirectBody)}, - connect: endpointResult{response: makeResponse(410, "")}, - }) - if nil != run { - t.Error(run) - } - if nil == resp.Err { - t.Fatal("missing error") - } - if !resp.IsDisconnect() { - t.Fatal("should be disconnect") - } -} - -func TestConnectAttemptBadSecurityPolicies(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, unknownRequiredPolicyBody)}, - connect: endpointResult{response: makeResponse(200, connectBody)}, - }) - if nil != run { - t.Error(run) - } - if nil == resp.Err { - t.Fatal("missing error") - } - if !resp.IsDisconnect() { - t.Fatal("should be disconnect") - } -} - -func TestConnectAttemptInvalidJSON(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, redirectBody)}, - connect: endpointResult{response: makeResponse(200, malformedBody)}, - }) - if nil != run { - t.Error(run) - } - if nil == resp.Err { - t.Fatal("missing error") - } -} - -func TestConnectAttemptCollectorNotString(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, `{"return_value":123}`)}, - connect: endpointResult{response: makeResponse(200, connectBody)}, - }) - if nil != run { - t.Error(run) - } - if nil == resp.Err { - t.Fatal("missing error") - } -} - -func TestConnectAttempt401(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, redirectBody)}, - connect: endpointResult{response: makeResponse(401, connectBody)}, - }) - if nil != run { - t.Error(run) - } - if nil == resp.Err { - t.Fatal("missing error") - } - if !resp.IsRestartException() { - t.Fatal("should be restart") - } -} - -func TestConnectAttemptOtherReturnCode(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, redirectBody)}, - connect: endpointResult{response: makeResponse(413, connectBody)}, - }) - if nil != run { - t.Error(run) - } - if nil == resp.Err { - t.Fatal("missing error") - } -} - -func TestConnectAttemptMissingRunID(t *testing.T) { - run, resp := testConnectHelper(connectMock{ - redirect: endpointResult{response: makeResponse(200, redirectBody)}, - connect: endpointResult{response: makeResponse(200, `{"return_value":{}}`)}, - }) - if nil != run { - t.Error(run) - } - if errMissingAgentRunID != resp.Err { - t.Fatal("wrong error", resp.Err) - } -} - -func TestCalculatePreconnectHost(t *testing.T) { - // non-region license - host := calculatePreconnectHost("0123456789012345678901234567890123456789", "") - if host != preconnectHostDefault { - t.Error(host) - } - // override present - override := "other-collector.newrelic.com" - host = calculatePreconnectHost("0123456789012345678901234567890123456789", override) - if host != override { - t.Error(host) - } - // four letter region - host = calculatePreconnectHost("eu01xx6789012345678901234567890123456789", "") - if host != "collector.eu01.nr-data.net" { - t.Error(host) - } - // five letter region - host = calculatePreconnectHost("gov01x6789012345678901234567890123456789", "") - if host != "collector.gov01.nr-data.net" { - t.Error(host) - } - // six letter region - host = calculatePreconnectHost("foo001x6789012345678901234567890123456789", "") - if host != "collector.foo001.nr-data.net" { - t.Error(host) - } -} - -func TestPreconnectHostCrossAgent(t *testing.T) { - var testcases []struct { - Name string `json:"name"` - ConfigFileKey string `json:"config_file_key"` - EnvKey string `json:"env_key"` - ConfigOverrideHost string `json:"config_override_host"` - EnvOverrideHost string `json:"env_override_host"` - ExpectHostname string `json:"hostname"` - } - err := crossagent.ReadJSON("collector_hostname.json", &testcases) - if err != nil { - t.Fatal(err) - } - - for _, tc := range testcases { - // mimic file/environment precedence of other agents - configKey := tc.ConfigFileKey - if "" != tc.EnvKey { - configKey = tc.EnvKey - } - overrideHost := tc.ConfigOverrideHost - if "" != tc.EnvOverrideHost { - overrideHost = tc.EnvOverrideHost - } - - host := calculatePreconnectHost(configKey, overrideHost) - if host != tc.ExpectHostname { - t.Errorf(`test="%s" got="%s" expected="%s"`, tc.Name, host, tc.ExpectHostname) - } - } -} - -func TestCollectorRequestRespectsMaxPayloadSize(t *testing.T) { - // Test that CollectorRequest returns an error when MaxPayloadSize is - // exceeded - cmd := RpmCmd{ - Name: "cmd_name", - Collector: "collector.com", - RunID: "run_id", - Data: []byte("abcdefghijklmnopqrstuvwxyz"), - MaxPayloadSize: 3, - } - cs := RpmControls{ - Client: &http.Client{ - Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) { - t.Error("no response should have gone out!") - return nil, nil - }), - }, - Logger: logger.ShimLogger{IsDebugEnabled: true}, - GzipWriterPool: &sync.Pool{ - New: func() interface{} { - return gzip.NewWriter(io.Discard) - }, - }, - } - resp := CollectorRequest(cmd, cs) - if nil == resp.Err { - t.Error("response should have contained error") - } - if resp.ShouldSaveHarvestData() { - t.Error("harvest data should be discarded when max_payload_size_in_bytes is exceeded") - } -} - -func TestConnectReplyMaxPayloadSize(t *testing.T) { - testcases := []struct { - replyBody string - expectedMaxPayloadSize int - }{ - { - replyBody: `{"return_value":{"agent_run_id":"my_agent_run_id"}}`, - expectedMaxPayloadSize: 1000 * 1000, - }, - { - replyBody: `{"return_value":{"agent_run_id":"my_agent_run_id","max_payload_size_in_bytes":123}}`, - expectedMaxPayloadSize: 123, - }, - } - - controls := func(replyBody string) RpmControls { - return RpmControls{ - Client: &http.Client{ - Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: 200, - Body: ioutil.NopCloser(strings.NewReader(replyBody)), - }, nil - }), - }, - Logger: logger.ShimLogger{IsDebugEnabled: true}, - GzipWriterPool: &sync.Pool{ - New: func() interface{} { - return gzip.NewWriter(io.Discard) - }, - }, - } - } - - for _, test := range testcases { - reply, resp := ConnectAttempt(testConfig{}, "", false, controls(test.replyBody)) - if nil != resp.Err { - t.Error("resp returned unexpected error:", resp.Err) - } - if test.expectedMaxPayloadSize != reply.MaxPayloadSizeInBytes { - t.Errorf("incorrect MaxPayloadSizeInBytes: expected=%d actual=%d", - test.expectedMaxPayloadSize, reply.MaxPayloadSizeInBytes) - } - } -} - -func TestPreconnectRequestMarshall(t *testing.T) { - tests := map[string]preconnectRequest{ - `[{"security_policies_token":"securityPoliciesToken","high_security":false}]`: { - SecurityPoliciesToken: "securityPoliciesToken", - HighSecurity: false, - }, - `[{"security_policies_token":"securityPoliciesToken","high_security":true}]`: { - SecurityPoliciesToken: "securityPoliciesToken", - HighSecurity: true, - }, - `[{"high_security":true}]`: { - SecurityPoliciesToken: "", - HighSecurity: true, - }, - `[{"high_security":false}]`: { - SecurityPoliciesToken: "", - HighSecurity: false, - }, - } - for expected, request := range tests { - b, e := json.Marshal([]preconnectRequest{request}) - if e != nil { - t.Fatal("Unable to marshall preconnect request", e) - } - result := string(b) - if result != expected { - t.Errorf("Invalid preconnect request marshall: expected %s, got %s", expected, result) - } - } -} diff --git a/internal/compress.go b/internal/compress.go deleted file mode 100644 index 130db67f5..000000000 --- a/internal/compress.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "compress/gzip" - "sync" -) - -func compress(b []byte, gzipWriterPool *sync.Pool) (*bytes.Buffer, error) { - w := gzipWriterPool.Get().(*gzip.Writer) - defer gzipWriterPool.Put(w) - - var buf bytes.Buffer - w.Reset(&buf) - _, err := w.Write(b) - w.Close() - - if nil != err { - return nil, err - } - - return &buf, nil -} diff --git a/internal/connect_reply.go b/internal/connect_reply.go deleted file mode 100644 index 2e318d220..000000000 --- a/internal/connect_reply.go +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "strings" - "time" -) - -// AgentRunID identifies the current connection with the collector. -type AgentRunID string - -func (id AgentRunID) String() string { - return string(id) -} - -// PreconnectReply contains settings from the preconnect endpoint. -type PreconnectReply struct { - Collector string `json:"redirect_host"` - SecurityPolicies SecurityPolicies `json:"security_policies"` -} - -// ConnectReply contains all of the settings and state send down from the -// collector. It should not be modified after creation. -type ConnectReply struct { - RunID AgentRunID `json:"agent_run_id"` - RequestHeadersMap map[string]string `json:"request_headers_map"` - MaxPayloadSizeInBytes int `json:"max_payload_size_in_bytes"` - EntityGUID string `json:"entity_guid"` - - // Transaction Name Modifiers - SegmentTerms segmentRules `json:"transaction_segment_terms"` - TxnNameRules metricRules `json:"transaction_name_rules"` - URLRules metricRules `json:"url_rules"` - MetricRules metricRules `json:"metric_name_rules"` - - // Cross Process - EncodingKey string `json:"encoding_key"` - CrossProcessID string `json:"cross_process_id"` - TrustedAccounts trustedAccountSet `json:"trusted_account_ids"` - - // Settings - KeyTxnApdex map[string]float64 `json:"web_transactions_apdex"` - ApdexThresholdSeconds float64 `json:"apdex_t"` - CollectAnalyticsEvents bool `json:"collect_analytics_events"` - CollectCustomEvents bool `json:"collect_custom_events"` - CollectTraces bool `json:"collect_traces"` - CollectErrors bool `json:"collect_errors"` - CollectErrorEvents bool `json:"collect_error_events"` - CollectSpanEvents bool `json:"collect_span_events"` - - // RUM - AgentLoader string `json:"js_agent_loader"` - Beacon string `json:"beacon"` - BrowserKey string `json:"browser_key"` - AppID string `json:"application_id"` - ErrorBeacon string `json:"error_beacon"` - JSAgentFile string `json:"js_agent_file"` - - // PreconnectReply fields are not in the connect reply, this embedding - // is done to simplify code. - PreconnectReply `json:"-"` - - Messages []struct { - Message string `json:"message"` - Level string `json:"level"` - } `json:"messages"` - - AdaptiveSampler AdaptiveSampler - // TraceIDGenerator creates random IDs for distributed tracing. It - // exists here in the connect reply so it can be modified to create - // deterministic identifiers in tests. - TraceIDGenerator *TraceIDGenerator `json:"-"` - - // BetterCAT/Distributed Tracing - AccountID string `json:"account_id"` - TrustedAccountKey string `json:"trusted_account_key"` - PrimaryAppID string `json:"primary_application_id"` - SamplingTarget uint64 `json:"sampling_target"` - SamplingTargetPeriodInSeconds int `json:"sampling_target_period_in_seconds"` - - // rulesCache caches the results of calling CreateFullTxnName. It - // exists here in ConnectReply since it is specific to a set of rules - // and is shared between transactions. - rulesCache *rulesCache - - ServerSideConfig struct { - TransactionTracerEnabled *bool `json:"transaction_tracer.enabled"` - // TransactionTracerThreshold should contain either a number or - // "apdex_f" if it is non-nil. - TransactionTracerThreshold interface{} `json:"transaction_tracer.transaction_threshold"` - TransactionTracerStackTraceThreshold *float64 `json:"transaction_tracer.stack_trace_threshold"` - ErrorCollectorEnabled *bool `json:"error_collector.enabled"` - ErrorCollectorIgnoreStatusCodes []int `json:"error_collector.ignore_status_codes"` - CrossApplicationTracerEnabled *bool `json:"cross_application_tracer.enabled"` - } `json:"agent_config"` - - // Faster Event Harvest - EventData EventHarvestConfig `json:"event_harvest_config"` -} - -// EventHarvestConfig contains fields relating to faster event harvest. -// This structure is used in the connect request (to send up defaults) -// and in the connect response (to get the server values). -// -// https://source.datanerd.us/agents/agent-specs/blob/master/Connect-LEGACY.md#event_harvest_config-hash -// https://source.datanerd.us/agents/agent-specs/blob/master/Connect-LEGACY.md#event-harvest-config -type EventHarvestConfig struct { - ReportPeriodMs int `json:"report_period_ms,omitempty"` - Limits struct { - TxnEvents *uint `json:"analytic_event_data,omitempty"` - CustomEvents *uint `json:"custom_event_data,omitempty"` - ErrorEvents *uint `json:"error_event_data,omitempty"` - SpanEvents *uint `json:"span_event_data,omitempty"` - } `json:"harvest_limits"` -} - -// ConfigurablePeriod returns the Faster Event Harvest configurable reporting period if it is set, or the default -// report period otherwise. -func (r *ConnectReply) ConfigurablePeriod() time.Duration { - ms := DefaultConfigurableEventHarvestMs - if nil != r && r.EventData.ReportPeriodMs > 0 { - ms = r.EventData.ReportPeriodMs - } - return time.Duration(ms) * time.Millisecond -} - -func uintPtr(x uint) *uint { return &x } - -// DefaultEventHarvestConfig provides faster event harvest defaults. -func DefaultEventHarvestConfig(eventer MaxTxnEventer) EventHarvestConfig { - cfg := EventHarvestConfig{} - cfg.ReportPeriodMs = DefaultConfigurableEventHarvestMs - cfg.Limits.TxnEvents = uintPtr(uint(eventer.MaxTxnEvents())) - cfg.Limits.CustomEvents = uintPtr(uint(MaxCustomEvents)) - cfg.Limits.ErrorEvents = uintPtr(uint(MaxErrorEvents)) - return cfg -} - -type trustedAccountSet map[int]struct{} - -func (t *trustedAccountSet) IsTrusted(account int) bool { - _, exists := (*t)[account] - return exists -} - -func (t *trustedAccountSet) UnmarshalJSON(data []byte) error { - accounts := make([]int, 0) - if err := json.Unmarshal(data, &accounts); err != nil { - return err - } - - *t = make(trustedAccountSet) - for _, account := range accounts { - (*t)[account] = struct{}{} - } - - return nil -} - -// ConnectReplyDefaults returns a newly allocated ConnectReply with the proper -// default settings. A pointer to a global is not used to prevent consumers -// from changing the default settings. -func ConnectReplyDefaults() *ConnectReply { - return &ConnectReply{ - ApdexThresholdSeconds: 0.5, - CollectAnalyticsEvents: true, - CollectCustomEvents: true, - CollectTraces: true, - CollectErrors: true, - CollectErrorEvents: true, - CollectSpanEvents: true, - MaxPayloadSizeInBytes: maxPayloadSizeInBytes, - // No transactions should be sampled before the application is - // connected. - AdaptiveSampler: SampleNothing{}, - - SamplingTarget: 10, - SamplingTargetPeriodInSeconds: 60, - - TraceIDGenerator: NewTraceIDGenerator(int64(time.Now().UnixNano())), - } -} - -// CalculateApdexThreshold calculates the apdex threshold. -func CalculateApdexThreshold(c *ConnectReply, txnName string) time.Duration { - if t, ok := c.KeyTxnApdex[txnName]; ok { - return FloatSecondsToDuration(t) - } - return FloatSecondsToDuration(c.ApdexThresholdSeconds) -} - -// CreateFullTxnName uses collector rules and the appropriate metric prefix to -// construct the full transaction metric name from the name given by the -// consumer. -func CreateFullTxnName(input string, reply *ConnectReply, isWeb bool) string { - if name := reply.rulesCache.find(input, isWeb); "" != name { - return name - } - name := constructFullTxnName(input, reply, isWeb) - if "" != name { - // Note that we don't cache situations where the rules say - // ignore. It would increase complication (we would need to - // disambiguate not-found vs ignore). Also, the ignore code - // path is probably extremely uncommon. - reply.rulesCache.set(input, isWeb, name) - } - return name -} - -func constructFullTxnName(input string, reply *ConnectReply, isWeb bool) string { - var afterURLRules string - if "" != input { - afterURLRules = reply.URLRules.Apply(input) - if "" == afterURLRules { - return "" - } - } - - prefix := backgroundMetricPrefix - if isWeb { - prefix = webMetricPrefix - } - - var beforeNameRules string - if strings.HasPrefix(afterURLRules, "/") { - beforeNameRules = prefix + afterURLRules - } else { - beforeNameRules = prefix + "/" + afterURLRules - } - - afterNameRules := reply.TxnNameRules.Apply(beforeNameRules) - if "" == afterNameRules { - return "" - } - - return reply.SegmentTerms.apply(afterNameRules) -} diff --git a/internal/connect_reply_test.go b/internal/connect_reply_test.go deleted file mode 100644 index 1261e8bc4..000000000 --- a/internal/connect_reply_test.go +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "testing" - "time" -) - -func TestCreateFullTxnNameBasic(t *testing.T) { - emptyReply := ConnectReplyDefaults() - - tcs := []struct { - input string - background bool - expect string - }{ - {"", true, "WebTransaction/Go/"}, - {"/", true, "WebTransaction/Go/"}, - {"hello", true, "WebTransaction/Go/hello"}, - {"/hello", true, "WebTransaction/Go/hello"}, - - {"", false, "OtherTransaction/Go/"}, - {"/", false, "OtherTransaction/Go/"}, - {"hello", false, "OtherTransaction/Go/hello"}, - {"/hello", false, "OtherTransaction/Go/hello"}, - } - - for _, tc := range tcs { - if out := CreateFullTxnName(tc.input, emptyReply, tc.background); out != tc.expect { - t.Error(tc.input, tc.background, out, tc.expect) - } - } -} - -func TestCreateFullTxnNameURLRulesIgnore(t *testing.T) { - js := `[{ - "match_expression":".*zip.*$", - "ignore":true - }]` - reply := ConnectReplyDefaults() - err := json.Unmarshal([]byte(js), &reply.URLRules) - if nil != err { - t.Fatal(err) - } - if out := CreateFullTxnName("/zap/zip/zep", reply, true); out != "" { - t.Error(out) - } -} - -func TestCreateFullTxnNameTxnRulesIgnore(t *testing.T) { - js := `[{ - "match_expression":"^WebTransaction/Go/zap/zip/zep$", - "ignore":true - }]` - reply := ConnectReplyDefaults() - err := json.Unmarshal([]byte(js), &reply.TxnNameRules) - if nil != err { - t.Fatal(err) - } - if out := CreateFullTxnName("/zap/zip/zep", reply, true); out != "" { - t.Error(out) - } -} - -func TestCreateFullTxnNameAllRulesWithCache(t *testing.T) { - js := `{ - "url_rules":[ - {"match_expression":"zip","each_segment":true,"replacement":"zoop"} - ], - "transaction_name_rules":[ - {"match_expression":"WebTransaction/Go/zap/zoop/zep", - "replacement":"WebTransaction/Go/zap/zoop/zep/zup/zyp"} - ], - "transaction_segment_terms":[ - {"prefix": "WebTransaction/Go/", - "terms": ["zyp", "zoop", "zap"]} - ] - }` - reply := ConnectReplyDefaults() - reply.rulesCache = newRulesCache(3) - err := json.Unmarshal([]byte(js), &reply) - if nil != err { - t.Fatal(err) - } - want := "WebTransaction/Go/zap/zoop/*/zyp" - if out := CreateFullTxnName("/zap/zip/zep", reply, true); out != want { - t.Error("wanted:", want, "got:", out) - } - // Check that the cache was populated as expected. - if out := reply.rulesCache.find("/zap/zip/zep", true); out != want { - t.Error("wanted:", want, "got:", out) - } - // Check that the next CreateFullTxnName returns the same output. - if out := CreateFullTxnName("/zap/zip/zep", reply, true); out != want { - t.Error("wanted:", want, "got:", out) - } -} - -func TestCalculateApdexThreshold(t *testing.T) { - reply := ConnectReplyDefaults() - threshold := CalculateApdexThreshold(reply, "WebTransaction/Go/hello") - if threshold != 500*time.Millisecond { - t.Error("default apdex threshold", threshold) - } - - reply = ConnectReplyDefaults() - reply.ApdexThresholdSeconds = 1.3 - reply.KeyTxnApdex = map[string]float64{ - "WebTransaction/Go/zip": 2.2, - "WebTransaction/Go/zap": 2.3, - } - threshold = CalculateApdexThreshold(reply, "WebTransaction/Go/hello") - if threshold != 1300*time.Millisecond { - t.Error(threshold) - } - threshold = CalculateApdexThreshold(reply, "WebTransaction/Go/zip") - if threshold != 2200*time.Millisecond { - t.Error(threshold) - } -} - -func TestIsTrusted(t *testing.T) { - for _, test := range []struct { - id int - trusted string - expected bool - }{ - {1, `[]`, false}, - {1, `[2, 3]`, false}, - {1, `[1]`, true}, - {1, `[1, 2, 3]`, true}, - } { - trustedAccounts := make(trustedAccountSet) - if err := json.Unmarshal([]byte(test.trusted), &trustedAccounts); err != nil { - t.Fatal(err) - } - - if actual := trustedAccounts.IsTrusted(test.id); test.expected != actual { - t.Errorf("failed asserting whether %d is trusted by %v: expected %v; got %v", test.id, test.trusted, test.expected, actual) - } - } -} - -func BenchmarkDefaultRules(b *testing.B) { - js := `{"url_rules":[ - { - "match_expression":".*\\.(ace|arj|ini|txt|udl|plist|css|gif|ico|jpe?g|js|png|swf|woff|caf|aiff|m4v|mpe?g|mp3|mp4|mov)$", - "replacement":"/*.\\1", - "ignore":false, - "eval_order":1000, - "terminate_chain":true, - "replace_all":false, - "each_segment":false - }, - { - "match_expression":"^[0-9][0-9a-f_,.-]*$", - "replacement":"*", - "ignore":false, - "eval_order":1001, - "terminate_chain":false, - "replace_all":false, - "each_segment":true - }, - { - "match_expression":"^(.*)/[0-9][0-9a-f_,-]*\\.([0-9a-z][0-9a-z]*)$", - "replacement":"\\1/.*\\2", - "ignore":false, - "eval_order":1002, - "terminate_chain":false, - "replace_all":false, - "each_segment":false - } - ]}` - reply := ConnectReplyDefaults() - reply.rulesCache = newRulesCache(1) - err := json.Unmarshal([]byte(js), &reply) - if nil != err { - b.Fatal(err) - } - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - if out := CreateFullTxnName("/myEndpoint", reply, true); out != "WebTransaction/Go/myEndpoint" { - b.Error(out) - } - } -} - -func TestNegativeHarvestLimits(t *testing.T) { - // Test that negative harvest event limits will cause a connect error. - // Harvest event limits are never expected to be negative: This is just - // extra defensiveness. - _, err := ConstructConnectReply([]byte(`{"return_value":{ - "event_harvest_config": { - "harvest_limits": { - "error_event_data": -1 - } - } - }}`), PreconnectReply{}) - if err == nil { - t.Fatal("expected error missing") - } -} - -type dfltMaxTxnEvents struct{} - -func (dfltMaxTxnEvents) MaxTxnEvents() int { - return MaxTxnEvents -} - -func TestDefaultEventHarvestConfigJSON(t *testing.T) { - js, err := json.Marshal(DefaultEventHarvestConfig(dfltMaxTxnEvents{})) - if err != nil { - t.Error(err) - } - if string(js) != `{"report_period_ms":60000,"harvest_limits":{"analytic_event_data":10000,"custom_event_data":10000,"error_event_data":100}}` { - t.Error(string(js)) - } -} diff --git a/internal/context.go b/internal/context.go deleted file mode 100644 index cd7ef2468..000000000 --- a/internal/context.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -type contextKeyType struct{} - -var ( - // TransactionContextKey is the key used for newrelic.FromContext and - // newrelic.NewContext. - TransactionContextKey = contextKeyType(struct{}{}) - - // GinTransactionContextKey is used as the context key in - // nrgin.Middleware and nrgin.Transaction. Unfortunately, Gin requires - // a string context key. We use two different context keys (and check - // both in nrgin.Transaction and newrelic.FromContext) rather than use a - // single string key because context.WithValue will fail golint if used - // with a string key. - GinTransactionContextKey = "newRelicTransaction" -) diff --git a/internal/cross_process_http.go b/internal/cross_process_http.go deleted file mode 100644 index 93b74e5dd..000000000 --- a/internal/cross_process_http.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "net/http" - - "github.com/newrelic/go-agent/internal/cat" -) - -// InboundHTTPRequest adds the inbound request metadata to the TxnCrossProcess. -func (txp *TxnCrossProcess) InboundHTTPRequest(hdr http.Header) error { - return txp.handleInboundRequestHeaders(HTTPHeaderToMetadata(hdr)) -} - -// AppDataToHTTPHeader encapsulates the given appData value in the correct HTTP -// header. -func AppDataToHTTPHeader(appData string) http.Header { - header := http.Header{} - - if appData != "" { - header.Add(cat.NewRelicAppDataName, appData) - } - - return header -} - -// HTTPHeaderToAppData gets the appData value from the correct HTTP header. -func HTTPHeaderToAppData(header http.Header) string { - if header == nil { - return "" - } - - return header.Get(cat.NewRelicAppDataName) -} - -// HTTPHeaderToMetadata gets the cross process metadata from the relevant HTTP -// headers. -func HTTPHeaderToMetadata(header http.Header) CrossProcessMetadata { - if header == nil { - return CrossProcessMetadata{} - } - - return CrossProcessMetadata{ - ID: header.Get(cat.NewRelicIDName), - TxnData: header.Get(cat.NewRelicTxnName), - Synthetics: header.Get(cat.NewRelicSyntheticsName), - } -} - -// MetadataToHTTPHeader creates a set of HTTP headers to represent the given -// cross process metadata. -func MetadataToHTTPHeader(metadata CrossProcessMetadata) http.Header { - header := http.Header{} - - if metadata.ID != "" { - header.Add(cat.NewRelicIDName, metadata.ID) - } - - if metadata.TxnData != "" { - header.Add(cat.NewRelicTxnName, metadata.TxnData) - } - - if metadata.Synthetics != "" { - header.Add(cat.NewRelicSyntheticsName, metadata.Synthetics) - } - - return header -} diff --git a/internal/cross_process_http_test.go b/internal/cross_process_http_test.go deleted file mode 100644 index 1d3409eb5..000000000 --- a/internal/cross_process_http_test.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "net/http" - "reflect" - "testing" - - "github.com/newrelic/go-agent/internal/cat" -) - -func TestTxnCrossProcessInitFromHTTPRequest(t *testing.T) { - txp := &TxnCrossProcess{} - txp.Init(true, false, replyAccountOne) - if txp.IsInbound() { - t.Error("inbound CAT enabled even though there was no request") - } - - txp = &TxnCrossProcess{} - req, err := http.NewRequest("GET", "http://foo.bar/", nil) - if err != nil { - t.Fatal(err) - } - txp.Init(true, false, replyAccountOne) - if err := txp.InboundHTTPRequest(req.Header); err != nil { - t.Errorf("got error while consuming an empty request: %v", err) - } - if txp.IsInbound() { - t.Error("inbound CAT enabled even though there was no metadata in the request") - } - - txp = &TxnCrossProcess{} - req, err = http.NewRequest("GET", "http://foo.bar/", nil) - if err != nil { - t.Fatal(err) - } - req.Header.Add(cat.NewRelicIDName, mustObfuscate(`1#1`, "foo")) - req.Header.Add(cat.NewRelicTxnName, mustObfuscate(`["abcdefgh",false,"12345678","b95be233"]`, "foo")) - txp.Init(true, false, replyAccountOne) - if err := txp.InboundHTTPRequest(req.Header); err != nil { - t.Errorf("got error while consuming an inbound CAT request: %v", err) - } - // A second call to InboundHTTPRequest to ensure that it can safely - // be called multiple times: - if err := txp.InboundHTTPRequest(req.Header); err != nil { - t.Errorf("got error while consuming an inbound CAT request: %v", err) - } - if !txp.IsInbound() { - t.Error("inbound CAT disabled even though there was metadata in the request") - } - if txp.ClientID != "1#1" { - t.Errorf("incorrect ClientID: %s", txp.ClientID) - } - if txp.ReferringTxnGUID != "abcdefgh" { - t.Errorf("incorrect ReferringTxnGUID: %s", txp.ReferringTxnGUID) - } - if txp.TripID != "12345678" { - t.Errorf("incorrect TripID: %s", txp.TripID) - } - if txp.ReferringPathHash != "b95be233" { - t.Errorf("incorrect ReferringPathHash: %s", txp.ReferringPathHash) - } -} - -func TestAppDataToHTTPHeader(t *testing.T) { - header := AppDataToHTTPHeader("") - if len(header) != 0 { - t.Errorf("unexpected number of header elements: %d", len(header)) - } - - header = AppDataToHTTPHeader("foo") - if len(header) != 1 { - t.Errorf("unexpected number of header elements: %d", len(header)) - } - if actual := header.Get(cat.NewRelicAppDataName); actual != "foo" { - t.Errorf("unexpected header value: %s", actual) - } -} - -func TestHTTPHeaderToAppData(t *testing.T) { - if appData := HTTPHeaderToAppData(nil); appData != "" { - t.Errorf("unexpected app data: %s", appData) - } - - header := http.Header{} - if appData := HTTPHeaderToAppData(header); appData != "" { - t.Errorf("unexpected app data: %s", appData) - } - - header.Add("X-Foo", "bar") - if appData := HTTPHeaderToAppData(header); appData != "" { - t.Errorf("unexpected app data: %s", appData) - } - - header.Add(cat.NewRelicAppDataName, "foo") - if appData := HTTPHeaderToAppData(header); appData != "foo" { - t.Errorf("unexpected app data: %s", appData) - } -} - -func TestHTTPHeaderToMetadata(t *testing.T) { - if metadata := HTTPHeaderToMetadata(nil); !reflect.DeepEqual(metadata, CrossProcessMetadata{}) { - t.Errorf("unexpected metadata: %v", metadata) - } - - header := http.Header{} - if metadata := HTTPHeaderToMetadata(header); !reflect.DeepEqual(metadata, CrossProcessMetadata{}) { - t.Errorf("unexpected metadata: %v", metadata) - } - - header.Add("X-Foo", "bar") - if metadata := HTTPHeaderToMetadata(header); !reflect.DeepEqual(metadata, CrossProcessMetadata{}) { - t.Errorf("unexpected metadata: %v", metadata) - } - - header.Add(cat.NewRelicIDName, "id") - if metadata := HTTPHeaderToMetadata(header); !reflect.DeepEqual(metadata, CrossProcessMetadata{ - ID: "id", - }) { - t.Errorf("unexpected metadata: %v", metadata) - } - - header.Add(cat.NewRelicTxnName, "txn") - if metadata := HTTPHeaderToMetadata(header); !reflect.DeepEqual(metadata, CrossProcessMetadata{ - ID: "id", - TxnData: "txn", - }) { - t.Errorf("unexpected metadata: %v", metadata) - } - - header.Add(cat.NewRelicSyntheticsName, "synth") - if metadata := HTTPHeaderToMetadata(header); !reflect.DeepEqual(metadata, CrossProcessMetadata{ - ID: "id", - TxnData: "txn", - Synthetics: "synth", - }) { - t.Errorf("unexpected metadata: %v", metadata) - } -} - -func TestMetadataToHTTPHeader(t *testing.T) { - metadata := CrossProcessMetadata{} - - header := MetadataToHTTPHeader(metadata) - if len(header) != 0 { - t.Errorf("unexpected number of header elements: %d", len(header)) - } - - metadata.ID = "id" - header = MetadataToHTTPHeader(metadata) - if len(header) != 1 { - t.Errorf("unexpected number of header elements: %d", len(header)) - } - if actual := header.Get(cat.NewRelicIDName); actual != "id" { - t.Errorf("unexpected header value: %s", actual) - } - - metadata.TxnData = "txn" - header = MetadataToHTTPHeader(metadata) - if len(header) != 2 { - t.Errorf("unexpected number of header elements: %d", len(header)) - } - if actual := header.Get(cat.NewRelicIDName); actual != "id" { - t.Errorf("unexpected header value: %s", actual) - } - if actual := header.Get(cat.NewRelicTxnName); actual != "txn" { - t.Errorf("unexpected header value: %s", actual) - } - - metadata.Synthetics = "synth" - header = MetadataToHTTPHeader(metadata) - if len(header) != 3 { - t.Errorf("unexpected number of header elements: %d", len(header)) - } - if actual := header.Get(cat.NewRelicIDName); actual != "id" { - t.Errorf("unexpected header value: %s", actual) - } - if actual := header.Get(cat.NewRelicTxnName); actual != "txn" { - t.Errorf("unexpected header value: %s", actual) - } - if actual := header.Get(cat.NewRelicSyntheticsName); actual != "synth" { - t.Errorf("unexpected header value: %s", actual) - } -} diff --git a/internal/crossagent/README.md b/internal/crossagent/README.md deleted file mode 100644 index 49b233213..000000000 --- a/internal/crossagent/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Cross Agent Tests - -At commit a4ec8e617340c8c7936d15ad18309ff5b9cfa93e. diff --git a/internal/crossagent/cross_agent_tests/README.md b/internal/crossagent/cross_agent_tests/README.md deleted file mode 100644 index 459c5023b..000000000 --- a/internal/crossagent/cross_agent_tests/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Cross Agent Tests - -### Data Policy - -None of these tests should contain customer data such as SQL strings. -Please be careful when adding new tests from real world failures. - -### Access - -Push access to this repository is granted via membership in the cross-agent-team GHE group. Contact Belinda Runkle if you are on the agent team but don't have push access. - -### Tests - -| Test Files | Description | -| ------------- |-------------| -| [rum_loader_insertion_location](rum_loader_insertion_location) | Describe where the RUM loader (formerly known as header) should be inserted. | -| [rum_footer_insertion_location](rum_footer_insertion_location) | Describe where the RUM footer (aka "client config") should be inserted. These tests do not apply to agents which insert the footer directly after the loader. | -| [rules.json](rules.json) | Describe how url/metric/txn-name rules should be applied. | -| [rum_client_config.json](rum_client_config.json) | These tests dictate the format and contents of the browser monitoring client configuration. For more information see: [SPEC](https://newrelic.atlassian.net/wiki/display/eng/BAM+Agent+Auto-Instrumentation) | -| [sql_parsing.json](sql_parsing.json) | These tests show how an SQL string should be parsed for the operation and table name. *Java Note*: The Java Agent is [out-of-sync with these tests](https://source.datanerd.us/java-agent/java_agent/blob/master/newrelic-agent/src/main/java/com/newrelic/agent/database/DefaultDatabaseStatementParser.java), [has its own tests](https://source.datanerd.us/java-agent/java_agent/blob/master/newrelic-agent/src/test/java/com/newrelic/agent/database/DatabaseStatementResponseParserTest.java), and cannot implement these without a breaking change. | -| [url_clean.json](url_clean.json) | These tests show how URLs should be cleaned before putting them into a trace segment's parameter hash (under the key 'uri'). | -| [url_domain_extraction.json](url_domain_extraction.json) | These tests show how the domain of a URL should be extracted (for the purpose of creating external metrics). | -| [postgres_explain_obfuscation](postgres_explain_obfuscation) | These tests show how plain-text explain plan output from PostgreSQL should be obfuscated when SQL obfuscation is enabled. | -| [sql_obfuscation](sql_obfuscation) | Describe how agents should obfuscate SQL queries before transmission to the collector. | -| [attribute_configuration](attribute_configuration.json) | These tests show how agents should respond to the various attribute configuration settings. For more information see: [Attributes SPEC](https://source.datanerd.us/agents/agent-specs/blob/master/Agent-Attributes-PORTED.md) | -| [cat](cat) | These tests cover the new Dirac attributes that are added for the CAT Map project. See the [CAT Spec](https://source.datanerd.us/agents/agent-specs/blob/master/Cross-Application-Tracing-PORTED.md) and the [README](cat/README.md) for details.| -| [labels](labels.json) | These tests cover the Labels for Language Agents project. See the [Labels for Language Agents Spec](https://newrelic.atlassian.net/wiki/display/eng/Labels+for+Language+Agents) for details.| -| [proc_cpuinfo](proc_cpuinfo) | These test correct processing of `/proc/cpuinfo` output on Linux hosts. | -| [proc_meminfo](proc_meminfo) | These test correct processing of `/proc/meminfo` output on Linux hosts. | -| [transaction_segment_terms.json](transaction_segment_terms.json) | These tests cover agent implementations of the `transaction_segment_terms` transaction renaming rules introduced in collector protocol 14. See [the spec](https://newrelic.atlassian.net/wiki/display/eng/Language+agent+transaction+segment+terms+rules) for details. | -| [synthetics](synthetics) | These tests cover agent support for Synthetics. For details, see [Agent Support for Synthetics: Forced Transaction Traces and Analytic Events](https://source.datanerd.us/agents/agent-specs/blob/master/Synthetics-PORTED.md). | -| [docker_container_id](docker_container_id) | These tests cover parsing of Docker container IDs from `/proc/*/cgroup` on Linux hosts. | -| [utilization](utilization) | These tests cover the collection and validation of metadata for billing purposes as per the [Utilization spec](https://source.datanerd.us/agents/agent-specs/blob/master/Utilization.md). | -| [utilization_vendor_specific](utilization_vendor_specific) | These tests cover the collection and validation of metadata for AWS, Pivotal Cloud Foundry, Google Cloud Platform, and Azure as per the [Utilization spec](https://source.datanerd.us/agents/agent-specs/blob/master/Utilization.md). | -| [distributed_tracing](distributed_tracing) | distributed tracing, a.k.a. CAT CATs | diff --git a/internal/crossagent/cross_agent_tests/attribute_configuration.json b/internal/crossagent/cross_agent_tests/attribute_configuration.json deleted file mode 100644 index c2b68673b..000000000 --- a/internal/crossagent/cross_agent_tests/attribute_configuration.json +++ /dev/null @@ -1,630 +0,0 @@ -[{ - "testname": "everything enabled, no include/exclude", - "config": { - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ] - }, - - { - "testname": "browser monitoring attributes disabled by default", - "config": {}, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector" - ] - }, - - { - "testname": "attributes globally disabled", - "config": { - "attributes.enabled": false, - "transaction_events.attributes.enabled": true, - "transaction_tracer.attributes.enabled": true, - "error_collector.attributes.enabled": true, - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "all categories disabled", - "config": { - "transaction_events.attributes.enabled": false, - "transaction_tracer.attributes.enabled": false, - "error_collector.attributes.enabled": false - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "global exclude", - "config": { - "attributes.exclude": ["alpha"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "exclude in each category", - "config": { - "transaction_events.attributes.exclude": ["alpha"], - "transaction_tracer.attributes.exclude": ["alpha"], - "error_collector.attributes.exclude": ["alpha"], - "browser_monitoring.attributes.enabled": true, - "browser_monitoring.attributes.exclude": ["alpha"] - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "global include", - "config": { - "attributes.include": ["alpha"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ] - }, - - { - "testname": "each category include", - "config": { - "transaction_events.attributes.include": ["alpha"], - "transaction_tracer.attributes.include": ["alpha"], - "error_collector.attributes.include": ["alpha"], - "browser_monitoring.attributes.enabled": true, - "browser_monitoring.attributes.include": ["alpha"] - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ] - }, - - { - "testname": "global include/exclude contradict", - "config": { - "attributes.exclude": ["alpha"], - "attributes.include": ["alpha"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "include/exclude contradict in each category", - "config": { - "transaction_events.attributes.exclude": ["alpha"], - "transaction_events.attributes.include": ["alpha"], - "transaction_tracer.attributes.exclude": ["alpha"], - "transaction_tracer.attributes.include": ["alpha"], - "error_collector.attributes.exclude": ["alpha"], - "error_collector.attributes.include": ["alpha"], - "browser_monitoring.attributes.enabled": true, - "browser_monitoring.attributes.exclude": ["alpha"], - "browser_monitoring.attributes.include": ["alpha"] - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "global exclude contradicts category include", - "config": { - "attributes.exclude": ["alpha"], - "transaction_events.attributes.include": ["alpha"], - "transaction_tracer.attributes.include": ["alpha"], - "error_collector.attributes.include": ["alpha"], - "browser_monitoring.attributes.enabled": true, - "browser_monitoring.attributes.include": ["alpha"] - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "global include contradicts category exclude", - "config": { - "attributes.include": ["alpha"], - "transaction_events.attributes.exclude": ["alpha"], - "transaction_tracer.attributes.exclude": ["alpha"], - "error_collector.attributes.exclude": ["alpha"], - "browser_monitoring.attributes.enabled": true, - "browser_monitoring.attributes.exclude": ["alpha"] - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "alpha is more specific than alpha*", - "config": { - "attributes.include": ["alpha"], - "transaction_events.attributes.exclude": ["alpha*"], - "transaction_tracer.attributes.exclude": ["alpha*"], - "error_collector.attributes.exclude": ["alpha*"], - "browser_monitoring.attributes.enabled": true, - "browser_monitoring.attributes.exclude": ["alpha*"] - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ] - }, - - { - "testname": "all destination modifiers applied, not only the most specific one", - "config": { - "attributes.exclude": ["a*"], - "transaction_events.attributes.include": ["ab*"], - "transaction_events.attributes.exclude": ["abc*"], - "transaction_tracer.attributes.exclude": ["abcd*"], - "transaction_tracer.attributes.include": ["abcde*"], - "error_collector.attributes.include": ["abcdef*"], - "error_collector.attributes.exclude": ["abcdefg*"], - "browser_monitoring.attributes.exclude": ["abcdefgh*"], - "browser_monitoring.attributes.include": ["abcdefghi*"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "abcdefghik", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_tracer", - "browser_monitoring" - ] - }, - - - { - "testname": "venn diagram part 1", - "config": { - "attributes.exclude": ["alpha.*", "alpha.beta.gamma.*"], - "attributes.include": ["alpha.beta.*"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha.", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "venn diagram part 2", - "config": { - "attributes.exclude": ["alpha.*", "alpha.beta.gamma.*"], - "attributes.include": ["alpha.beta.*"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha.psi", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "venn diagram part 3", - "config": { - "attributes.exclude": ["alpha.*", "alpha.beta.gamma.*"], - "attributes.include": ["alpha.beta.*"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha.beta.", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ] - }, - - { - "testname": "venn diagram part 4", - "config": { - "attributes.exclude": ["alpha.*", "alpha.beta.gamma.*"], - "attributes.include": ["alpha.beta.*"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha.beta.psi", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ] - }, - - { - "testname": "venn diagram part 5", - "config": { - "attributes.exclude": ["alpha.*", "alpha.beta.gamma.*"], - "attributes.include": ["alpha.beta.*"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha.beta.gamma.", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "alpha is not mistaken for alpha*", - "config": { - "transaction_events.attributes.include": ["alpha"], - "transaction_tracer.attributes.exclude": ["alpha*"], - "error_collector.attributes.include": ["alpha"], - "browser_monitoring.attributes.exclude": ["alpha*"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "alpha.beta", - "input_default_destinations": [ - "transaction_tracer", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "exact match is case sensitive", - "config": { - "attributes.exclude": ["alpha"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "ALPHA", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ] - }, - - { - "testname": "wildcard match is case sensitive", - "config": { - "attributes.exclude": ["alpha.*"], - "browser_monitoring.attributes.enabled": true - }, - "input_key": "ALPHA.BETA", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ] - }, - - { - "testname": "include with attributes globally disabled", - "config": { - "attributes.enabled": false, - "transaction_events.attributes.include": ["alpha"], - "transaction_tracer.attributes.include": ["alpha"], - "error_collector.attributes.include": ["alpha"], - "browser_monitoring.attributes.include": ["alpha"] - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - ] - }, - - { - "testname": "include with disabled destinations", - "config": { - "transaction_events.attributes.include": ["alpha"], - "transaction_events.attributes.enabled": false, - "transaction_tracer.attributes.include": ["alpha"], - "error_collector.attributes.include": ["alpha"], - "browser_monitoring.attributes.enabled": true, - "browser_monitoring.attributes.include": ["alpha"] - }, - "input_key": "alpha", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer", - "error_collector", - "browser_monitoring" - ], - "expected_destinations": [ - "transaction_tracer", - "error_collector", - "browser_monitoring" - ] - }, - - { - "testname": "ordering of rules should not matter 1", - "config": { - "transaction_events.attributes.include": ["b*", "bcd*"], - "transaction_events.attributes.exclude": ["bc*"] - }, - "input_key": "b", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer" - ] - }, - - { - "testname": "ordering of rules should not matter 2", - "config": { - "transaction_events.attributes.include": ["b*", "bcd*"], - "transaction_events.attributes.exclude": ["bc*"] - }, - "input_key": "bc", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer" - ], - "expected_destinations": [ - "transaction_tracer" - ] - }, - - { - "testname": "ordering of rules should not matter 3", - "config": { - "transaction_events.attributes.include": ["b*", "bcd*"], - "transaction_events.attributes.exclude": ["bc*"] - }, - "input_key": "bcd", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer" - ] - }, - - { - "testname": "ordering of rules should not matter 4", - "config": { - "transaction_events.attributes.include": ["b*", "bcd*"], - "transaction_events.attributes.exclude": ["bc*"] - }, - "input_key": "bcde", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer" - ] - }, - - { - "testname": "ordering of rules should not matter 5", - "config": { - "transaction_events.attributes.include": ["bcd*", "b*"], - "transaction_events.attributes.exclude": ["bc*"] - }, - "input_key": "b", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer" - ] - }, - - { - "testname": "ordering of rules should not matter 6", - "config": { - "transaction_events.attributes.include": ["bcd*", "b*"], - "transaction_events.attributes.exclude": ["bc*"] - }, - "input_key": "bc", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer" - ], - "expected_destinations": [ - "transaction_tracer" - ] - }, - - { - "testname": "ordering of rules should not matter 7", - "config": { - "transaction_events.attributes.include": ["bcd*", "b*"], - "transaction_events.attributes.exclude": ["bc*"] - }, - "input_key": "bcd", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer" - ] - }, - - { - "testname": "ordering of rules should not matter 8", - "config": { - "transaction_events.attributes.include": ["bcd*", "b*"], - "transaction_events.attributes.exclude": ["bc*"] - }, - "input_key": "bcde", - "input_default_destinations": [ - "transaction_events", - "transaction_tracer" - ], - "expected_destinations": [ - "transaction_events", - "transaction_tracer" - ] - } -] diff --git a/internal/crossagent/cross_agent_tests/cat/README.md b/internal/crossagent/cross_agent_tests/cat/README.md deleted file mode 100644 index 60a88c1d4..000000000 --- a/internal/crossagent/cross_agent_tests/cat/README.md +++ /dev/null @@ -1,28 +0,0 @@ -### CAT Map test details - -The CAT map test cases in `cat_map.json` are meant to be used to verify the -attributes that agents collect and attach to analytics transaction events for -the CAT map project. - -**NOTE** currently `nr.apdexPerfZone` is not covered by these tests, make sure you test for this yourself until it is added to these tests. - -Each test case should correspond to a simulated transaction in the agent under -test. Here's what the various fields in each test case mean: - -| Name | Meaning | -| ---- | ------- | -| `name` | A human-meaningful name for the test case. | -| `appName` | The name of the New Relic application for the simulated transaction. | -| `transactionName` | The final name of the simulated transaction. | -| `transactionGuid` | The GUID of the simulated transaction. | -| `inboundPayload` | The (non-serialized) contents of the `X-NewRelic-Transaction` HTTP request header on the simulated transaction. Note that this value should be serialized to JSON, obfuscated using the CAT obfuscation algorithm, and Base64-encoded before being used in the header value. Note also that the `X-NewRelic-ID` header should be set on the simulated transaction, though its value is not specified in these tests. | -| `expectedIntrinsicFields` | A set of key-value pairs that are expected to be present in the analytics event generated for the simulated transaction. These fields should be present in the first hash of the analytic event payload (built-in agent-supplied fields). | -| `nonExpectedIntrinsicFields` | An array of attribute names that should *not* be present in the analytics event generated for the simulated transaction. | -| `outboundRequests` | An array of objects representing outbound requests that should be made in the context of the simulated transaction. See the table below for details. Only present if the test case involves making outgoing requests from the simulated transaction. | - -Here's what the fields of each entry in the `outboundRequests` array mean: - -| Name | Meaning | -| ---- | ------- | -| `outboundTxnName` | The name of the simulated transaction at the time this outbound request is made. Your test driver should set the transaction name to this value prior to simulating the outbound request. | -| `expectedOutboundPayload` | The expected (un-obfuscated) content of the outbound `X-NewRelic-Transaction` request header for this request. | diff --git a/internal/crossagent/cross_agent_tests/cat/cat_map.json b/internal/crossagent/cross_agent_tests/cat/cat_map.json deleted file mode 100644 index 15c33ba88..000000000 --- a/internal/crossagent/cross_agent_tests/cat/cat_map.json +++ /dev/null @@ -1,595 +0,0 @@ -[ - { - "name": "new_cat", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - "7e249074f277923d", - "5d2957be" - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "7e249074f277923d", - "nr.pathHash": "815b96d3", - "nr.referringTransactionGuid": "b854df4feb2b1f06", - "nr.referringPathHash": "5d2957be" - }, - "nonExpectedIntrinsicFields": [ - "nr.alternatePathHashes" - ] - }, - { - "name": "new_cat_path_hash_with_leading_zero", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/txn4", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - "7e249074f277923d", - "5d2957be" - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "7e249074f277923d", - "nr.pathHash": "0e258e4e", - "nr.referringTransactionGuid": "b854df4feb2b1f06", - "nr.referringPathHash": "5d2957be" - }, - "nonExpectedIntrinsicFields": [ - "nr.alternatePathHashes" - ] - }, - { - "name": "new_cat_path_hash_with_unicode_name", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/txn\u221a\u221a\u221a", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - "7e249074f277923d", - "5d2957be" - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "7e249074f277923d", - "nr.pathHash": "3d015d23", - "nr.referringTransactionGuid": "b854df4feb2b1f06", - "nr.referringPathHash": "5d2957be" - }, - "nonExpectedIntrinsicFields": [ - "nr.alternatePathHashes" - ] - }, - { - "name": "new_cat_no_referring_payload", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": null, - "expectedIntrinsicFields": {}, - "nonExpectedIntrinsicFields": [ - "nr.guid", - "nr.tripId", - "nr.pathHash", - "nr.referringTransactionGuid", - "nr.referringPathHash", - "nr.alternatePathHashes" - ] - }, - { - "name": "new_cat_with_call_out", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": null, - "outboundRequests": [ - { - "outboundTxnName": "WebTransaction/Custom/testTxnName", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "3b0939af" - ] - } - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "9323dc260548ed0e", - "nr.pathHash": "3b0939af" - }, - "nonExpectedIntrinsicFields": [ - "nr.referringTransactionGuid", - "nr.referringPathHash", - "nr.alternatePathHashes" - ] - }, - { - "name": "new_cat_with_multiple_calls_out", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": null, - "outboundRequests": [ - { - "outboundTxnName": "WebTransaction/Custom/otherTxnName", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "f1c8adf5" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/otherTxnName", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "f1c8adf5" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/moreOtherTxnName", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "ea19b61c" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/moreDifferentTxnName", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "e00736cc" - ] - } - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "9323dc260548ed0e", - "nr.pathHash": "3b0939af", - "nr.alternatePathHashes": "e00736cc,ea19b61c,f1c8adf5" - }, - "nonExpectedIntrinsicFields": [ - "nr.referringTransactionGuid", - "nr.referringPathHash" - ] - }, - { - "name": "new_cat_with_many_unique_calls_out", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": null, - "outboundRequests": [ - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn2", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "a67c2da4" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn3", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "0d932b2b" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn4", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "b4772132" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn5", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "51a1a337" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn6", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "77b5cb70" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn7", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "8a842c7f" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn8", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "b968edb8" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn9", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "2691f90e" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn10", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "b46aec87" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn11", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "10bb3bf3" - ] - } - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "9323dc260548ed0e", - "nr.pathHash": "3b0939af", - "nr.alternatePathHashes": "0d932b2b,2691f90e,51a1a337,77b5cb70,8a842c7f,93fb4310,a67c2da4,b46aec87,b4772132,b968edb8" - }, - "nonExpectedIntrinsicFields": [ - "nr.referringTransactionGuid", - "nr.referringPathHash" - ] - }, - { - "name": "new_cat_with_many_calls_out", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": null, - "outboundRequests": [ - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn1", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "93fb4310" - ] - }, - { - "outboundTxnName": "WebTransaction/Custom/txn2", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "9323dc260548ed0e", - "a67c2da4" - ] - } - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "9323dc260548ed0e", - "nr.pathHash": "3b0939af", - "nr.alternatePathHashes": "93fb4310,a67c2da4" - }, - "nonExpectedIntrinsicFields": [ - "nr.referringTransactionGuid", - "nr.referringPathHash" - ] - }, - { - "name": "new_cat_with_referring_info_and_call_out", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - "7e249074f277923d", - "5d2957be" - ], - "outboundRequests": [ - { - "outboundTxnName": "WebTransaction/Custom/otherTxnName", - "expectedOutboundPayload": [ - "9323dc260548ed0e", - false, - "7e249074f277923d", - "4b9a0289" - ] - } - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "7e249074f277923d", - "nr.pathHash": "815b96d3", - "nr.alternatePathHashes": "4b9a0289", - "nr.referringTransactionGuid": "b854df4feb2b1f06", - "nr.referringPathHash": "5d2957be" - }, - "nonExpectedIntrinsicFields": [] - }, - { - "name": "new_cat_missing_path_hash", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - "7e249074f277923d" - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "7e249074f277923d", - "nr.pathHash": "3b0939af", - "nr.referringTransactionGuid": "b854df4feb2b1f06" - }, - "nonExpectedIntrinsicFields": [ - "nr.alternatePathHashes", - "nr.referringPathHash" - ] - }, - { - "name": "new_cat_null_path_hash", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - "7e249074f277923d", - null - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "7e249074f277923d", - "nr.pathHash": "3b0939af", - "nr.referringTransactionGuid": "b854df4feb2b1f06" - }, - "nonExpectedIntrinsicFields": [ - "nr.alternatePathHashes", - "nr.referringPathHash" - ] - }, - { - "name": "new_cat_malformed_path_hash", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - "7e249074f277923d", - [ - "scrambled", - "eggs" - ] - ], - "expectedIntrinsicFields": {}, - "nonExpectedIntrinsicFields": [ - "nr.guid", - "nr.tripId", - "nr.pathHash", - "nr.referringTransactionGuid", - "nr.referringPathHash", - "nr.alternatePathHashes" - ] - }, - { - "name": "new_cat_corrupt_path_hash", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - "7e249074f277923d", - "ZXYQEDABC" - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "7e249074f277923d", - "nr.pathHash": "3b0939af", - "nr.referringTransactionGuid": "b854df4feb2b1f06", - "nr.referringPathHash": "ZXYQEDABC" - }, - "nonExpectedIntrinsicFields": [ - "nr.alternatePathHashes" - ] - }, - { - "name": "new_cat_malformed_trip_id", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - ["scrambled"], - "5d2957be" - ], - "expectedIntrinsicFields": {}, - "nonExpectedIntrinsicFields": [ - "nr.guid", - "nr.tripId", - "nr.pathHash", - "nr.referringTransactionGuid", - "nr.referringPathHash", - "nr.alternatePathHashes" - ] - }, - { - "name": "new_cat_missing_trip_id", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "9323dc260548ed0e", - "nr.pathHash": "3b0939af", - "nr.referringTransactionGuid": "b854df4feb2b1f06" - }, - "nonExpectedIntrinsicFields": [ - "nr.referringPathHash", - "nr.alternatePathHashes" - ] - }, - { - "name": "new_cat_null_trip_id", - "appName": "testAppName", - "transactionName": "WebTransaction/Custom/testTxnName", - "transactionGuid": "9323dc260548ed0e", - "inboundPayload": [ - "b854df4feb2b1f06", - false, - null - ], - "expectedIntrinsicFields": { - "nr.guid": "9323dc260548ed0e", - "nr.tripId": "9323dc260548ed0e", - "nr.pathHash": "3b0939af", - "nr.referringTransactionGuid": "b854df4feb2b1f06" - }, - "nonExpectedIntrinsicFields": [ - "nr.alternatePathHashes", - "nr.referringPathHash" - ] - } -] diff --git a/internal/crossagent/cross_agent_tests/cat/path_hashing.json b/internal/crossagent/cross_agent_tests/cat/path_hashing.json deleted file mode 100644 index 2bc760d29..000000000 --- a/internal/crossagent/cross_agent_tests/cat/path_hashing.json +++ /dev/null @@ -1,51 +0,0 @@ -[ - { - "name": "no referring path hash", - "referringPathHash": null, - "applicationName": "application A", - "transactionName": "transaction A", - "expectedPathHash": "5e17050e" - }, - { - "name": "leading zero on resulting path hash", - "referringPathHash": null, - "applicationName": "my application", - "transactionName": "transaction 13", - "expectedPathHash": "097ca5e1" - }, - { - "name": "with referring path hash", - "referringPathHash": "95f2f716", - "applicationName": "app2", - "transactionName": "txn2", - "expectedPathHash": "ef72c2e6" - }, - { - "name": "with referring path hash leading zero", - "referringPathHash": "077634eb", - "applicationName": "app3", - "transactionName": "txn3", - "expectedPathHash": "bfd6587f" - }, - { - "name": "with multi-byte UTF-8 characters in transaction name", - "referringPathHash": "95f2f716", - "applicationName": "app1", - "transactionName": "Доверяй, но проверяй", - "expectedPathHash": "b7ad900e" - }, - { - "name": "high bit of referringPathHash set", - "referringPathHash": "80000000", - "applicationName": "app1", - "transactionName": "txn1", - "expectedPathHash": "95f2f717" - }, - { - "name": "low bit of referringPathHash set", - "referringPathHash": "00000001", - "applicationName": "app1", - "transactionName": "txn1", - "expectedPathHash": "95f2f714" - } -] diff --git a/internal/crossagent/cross_agent_tests/collector_hostname.json b/internal/crossagent/cross_agent_tests/collector_hostname.json deleted file mode 100644 index ff381c889..000000000 --- a/internal/crossagent/cross_agent_tests/collector_hostname.json +++ /dev/null @@ -1,71 +0,0 @@ -[ - { - "name": "normal license key", - "config_file_key": "08a2ad66c637a29c3982469a3fe8d1982d002c4a", - "hostname": "collector.newrelic.com" - }, - { - "name": "region aware key with four character identifier", - "config_file_key": "eu01xx66c637a29c3982469a3fe8d1982d002c4a", - "hostname": "collector.eu01.nr-data.net" - }, - { - "name": "region aware key with five character identifier", - "config_file_key": "gov01x66c637a29c3982469a3fe8d1982d002c4a", - "hostname": "collector.gov01.nr-data.net" - }, - { - "name": "region aware key with seven character identifier", - "config_file_key": "foo1234xc637a29c3982469a3fe8d1982d002c4a", - "hostname": "collector.foo1234.nr-data.net" - }, - { - "name": "region aware key with abnormal identifier", - "config_file_key": "20foox66c637a29c3982469a3fe8d1982d002c4a", - "hostname": "collector.20foo.nr-data.net" - }, - { - "name": "region aware key with more than one identifier", - "config_file_key": "eu01xeu02x37a29c3982469a3fe8d1982d002c4a", - "hostname": "collector.eu01.nr-data.net" - }, - { - "name": "environment variable specified license key", - "env_key": "eu01xx66c637a29c3982469a3fe8d1982d002c4a", - "hostname": "collector.eu01.nr-data.net" - }, - { - "name": "env var host override", - "config_file_key": "eu01xx66c637a29c3982469a3fe8d1982d002c4a", - "env_override_host": "other-collector.newrelic.com", - "hostname": "other-collector.newrelic.com" - }, - { - "name": "local config host override", - "config_file_key": "eu01xx66c637a29c3982469a3fe8d1982d002c4a", - "config_override_host": "other-collector.newrelic.com", - "hostname": "other-collector.newrelic.com" - }, - { - "name": "local config host override with env key", - "env_key": "eu01xx66c637a29c3982469a3fe8d1982d002c4a", - "config_override_host": "other-collector.newrelic.com", - "hostname": "other-collector.newrelic.com" - }, - { - "name": "env var host override with env key", - "env_key": "eu01xx66c637a29c3982469a3fe8d1982d002c4a", - "env_override_host": "other-collector.newrelic.com", - "hostname": "other-collector.newrelic.com" - }, - { - "name": "env var host override default with default env key", - "env_key": "eu01xx66c637a29c3982469a3fe8d1982d002c4a", - "env_override_host": "collector.newrelic.com", - "hostname": "collector.newrelic.com" - }, - { - "name": "No specified key defaults to collector.newrelic.com", - "hostname": "collector.newrelic.com" - } -] diff --git a/internal/crossagent/cross_agent_tests/data_collection_server_configuration.json b/internal/crossagent/cross_agent_tests/data_collection_server_configuration.json deleted file mode 100644 index 7a0452240..000000000 --- a/internal/crossagent/cross_agent_tests/data_collection_server_configuration.json +++ /dev/null @@ -1,219 +0,0 @@ -[ - { - "test_name": "collect_span_events_disabled", - "connect_response": { - "collect_span_events": false - }, - "expected_data_seen": [ - { - "type": "span_event", - "count": 0 - } - ], - "expected_endpoint_calls": [ - { - "method": "span_event_data", - "count": 0 - } - ] - }, - { - "test_name": "collect_span_events_enabled", - "connect_response": { - "collect_span_events": true - }, - "expected_data_seen": [ - { - "type": "span_event", - "count": 1 - } - ], - "expected_endpoint_calls": [ - { - "method": "span_event_data", - "count": 1 - } - ] - }, - { - "test_name": "collect_custom_events_disabled", - "connect_response": { - "collect_custom_events": false - }, - "expected_data_seen": [ - { - "type": "custom_event", - "count": 0 - } - ], - "expected_endpoint_calls": [ - { - "method": "custom_event_data", - "count": 0 - } - ] - }, - { - "test_name": "collect_custom_events_enabled", - "connect_response": { - "collect_custom_events": true - }, - "expected_data_seen": [ - { - "type": "custom_event", - "count": 1 - } - ], - "expected_endpoint_calls": [ - { - "method": "custom_event_data", - "count": 1 - } - ] - }, - { - "test_name": "collect_analytics_events_disabled", - "connect_response": { - "collect_analytics_events": false - }, - "expected_data_seen": [ - { - "type": "transaction_event", - "count": 0 - } - ], - "expected_endpoint_calls": [ - { - "method": "analytic_event_data", - "count": 0 - } - ] - }, - { - "test_name": "collect_analytics_events_enabled", - "connect_response": { - "collect_analytics_events": true - }, - "expected_data_seen": [ - { - "type": "transaction_event", - "count": 1 - } - ], - - "expected_endpoint_calls": [ - { - "method": "analytic_event_data", - "count": 1 - } - ] - }, - { - "test_name": "collect_error_events_disabled", - "connect_response": { - "collect_error_events": false - }, - "expected_data_seen": [ - { - "type": "error_event", - "count": 0 - } - ], - "expected_endpoint_calls": [ - { - "method": "error_event_data", - "count": 0 - } - ] - }, - { - "test_name": "collect_error_events_enabled", - "connect_response": { - "collect_error_events": true - }, - "expected_data_seen": [ - { - "type": "error_event", - "count": 1 - } - ], - "expected_endpoint_calls": [ - { - "method": "error_event_data", - "count": 1 - } - ] - }, - { - "test_name": "collect_errors_disabled", - "connect_response": { - "collect_errors": false - }, - "expected_data_seen": [ - { - "type": "error_trace", - "count": 0 - } - ], - "expected_endpoint_calls": [ - { - "method": "error_data", - "count": 0 - } - ] - }, - { - "test_name": "collect_errors_enabled", - "connect_response": { - "collect_errors": true - }, - "expected_data_seen": [ - { - "type": "error_trace", - "count": 1 - } - ], - "expected_endpoint_calls": [ - { - "method": "error_data", - "count": 1 - } - ] - }, - { - "test_name": "collect_traces_disabled", - "connect_response": { - "collect_traces": false - }, - "expected_data_seen": [ - { - "type": "transaction_trace", - "count": 0 - } - ], - "expected_endpoint_calls": [ - { - "method": "transaction_sample_data", - "count": 0 - } - ] - }, - { - "test_name": "collect_traces_enabled", - "connect_response": { - "collect_traces": true - }, - "expected_data_seen": [ - { - "type": "transaction_trace", - "count": 1 - } - ], - "expected_endpoint_calls": [ - { - "method": "transaction_sample_data", - "count": 1 - } - ] - } -] diff --git a/internal/crossagent/cross_agent_tests/datastores/README.md b/internal/crossagent/cross_agent_tests/datastores/README.md deleted file mode 100644 index a38115b51..000000000 --- a/internal/crossagent/cross_agent_tests/datastores/README.md +++ /dev/null @@ -1,16 +0,0 @@ -## Datastore instance tests - -The datastore instance tests provide attributes similar to what an agent could expect to find regarding a database configuration and specifies the expected [datastore instance metric](https://source.datanerd.us/agents/agent-specs/blob/master/Datastore-Metrics-PORTED.md#datastore-metric-namespace) that should be generated. The table below lists types attributes and whether will will always be included or optionally included in each test case. - -| Name | Present | Description | -|---|---|---| -| system_hostname | always | the hostname of the machine | -| db_hostname | sometimes | the hostname reported by the database adapter | -| product | always | the database product for this configuration -| port | sometimes | the port reported by the database adapter | -| unix_socket | sometimes |the path to a unix domain socket reported by a database adapter | -| database_path | sometimes |the path to a filesystem database | -| expected\_instance\_metric | always | the instance metric expected to be generated from the given attributes | - -## Implementing the test cases -The idea behind these test cases are that you are able to determine a set of configuration properties from a database connection, and based on those properties you should generate the `expected_instance_metric`. Sometimes the properties available are minimal and will mean that you will need to fall back to defaults to obtain some of the information. When there is missing information from a database adapter the guiding principle is to fill in the defaults when they can be inferred, but do not make guesses that could be incorrect or misleading. Some agents may have access to better data and may not need to make inferences. If this applies to your agent then many of these tests will not be applicable. diff --git a/internal/crossagent/cross_agent_tests/datastores/datastore_api.json b/internal/crossagent/cross_agent_tests/datastores/datastore_api.json deleted file mode 100644 index c07e2b6d2..000000000 --- a/internal/crossagent/cross_agent_tests/datastores/datastore_api.json +++ /dev/null @@ -1,443 +0,0 @@ -[ - { - "test_name": "all required fields present, everything enabled", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"my_db" - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT", - "Datastore/instance/MySQL/db-server-1/3306" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"my_db" - } - } - }, - { - "test_name": "database name missing", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "host":"db-server-1", - "port_path_or_id":"3306" - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT", - "Datastore/instance/MySQL/db-server-1/3306" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "host":"db-server-1", - "port_path_or_id":"3306" - } - } - }, - { - "test_name": "host and port missing", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "database_name":"my_db" - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "database_name":"my_db" - } - } - }, - { - "test_name": "host missing, but port present", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "port_path_or_id":"3306", - "database_name":"my_db" - }, - "is_web":true, - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT", - "Datastore/instance/MySQL/unknown/3306" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "host":"unknown", - "port_path_or_id":"3306", - "database_name":"my_db" - } - } - }, - { - "test_name": "instance reporting false", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"my_db" - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":false, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "database_name":"my_db" - } - } - }, - { - "test_name": "database name disabled", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"my_db" - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":false - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT", - "Datastore/instance/MySQL/db-server-1/3306" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "host":"db-server-1", - "port_path_or_id":"3306" - } - } - }, - { - "test_name": "all fields missing", - "input":{ - "parameters":{ - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/Unknown/all", - "Datastore/Unknown/allWeb", - "Datastore/operation/Unknown/other" - ], - "metrics_scoped":[ - "Datastore/operation/Unknown/other" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/operation/Unknown/other" - } - } - }, - { - "test_name": "missing collection", - "input":{ - "parameters":{ - "product":"MySQL", - "operation":"INSERT", - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"my_db" - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/instance/MySQL/db-server-1/3306" - ], - "metrics_scoped":[ - "Datastore/operation/MySQL/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/operation/MySQL/INSERT", - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"my_db" - } - } - }, - { - "test_name": "host present, port missing", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "host":"db-server-1", - "database_name":"my_db" - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT", - "Datastore/instance/MySQL/db-server-1/unknown" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "host":"db-server-1", - "port_path_or_id":"unknown", - "database_name":"my_db" - } - } - }, - { - "test_name": "localhost replacement", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "host":"localhost", - "port_path_or_id":"3306", - "database_name":"my_db" - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT", - "Datastore/instance/MySQL/datanerd-01/3306" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "host":"datanerd-01", - "port_path_or_id":"3306", - "database_name":"my_db" - } - } - }, - { - "test_name": "background transaction", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"my_db" - }, - "is_web":false, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allOther", - "Datastore/MySQL/all", - "Datastore/MySQL/allOther", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT", - "Datastore/instance/MySQL/db-server-1/3306" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"my_db" - } - } - }, - { - "test_name": "socket path port", - "input":{ - "parameters":{ - "product":"MySQL", - "collection":"users", - "operation":"INSERT", - "host":"db-server-1", - "port_path_or_id":"/var/mysql/mysql.sock", - "database_name":"my_db" - }, - "is_web":true, - "system_hostname":"datanerd-01", - "configuration":{ - "datastore_tracer.instance_reporting.enabled":true, - "datastore_tracer.database_name_reporting.enabled":true - } - }, - "expectation":{ - "metrics_unscoped":[ - "Datastore/all", - "Datastore/allWeb", - "Datastore/MySQL/all", - "Datastore/MySQL/allWeb", - "Datastore/operation/MySQL/INSERT", - "Datastore/statement/MySQL/users/INSERT", - "Datastore/instance/MySQL/db-server-1//var/mysql/mysql.sock" - ], - "metrics_scoped":[ - "Datastore/statement/MySQL/users/INSERT" - ], - "transaction_segment_and_slow_query_trace":{ - "metric_name":"Datastore/statement/MySQL/users/INSERT", - "host":"db-server-1", - "port_path_or_id":"/var/mysql/mysql.sock", - "database_name":"my_db" - } - } - } -] diff --git a/internal/crossagent/cross_agent_tests/datastores/datastore_instances.json b/internal/crossagent/cross_agent_tests/datastores/datastore_instances.json deleted file mode 100644 index 55dbd10c1..000000000 --- a/internal/crossagent/cross_agent_tests/datastores/datastore_instances.json +++ /dev/null @@ -1,73 +0,0 @@ -[ - { - "name": "instance metric uses system hostname when db reports localhost", - "system_hostname": "datanerd-01", - "db_hostname": "localhost", - "product": "Postgres", - "port": 5432, - "expected_instance_metric": "Datastore/instance/Postgres/datanerd-01/5432" - }, - { - "name": "instance metric uses system hostname when db reports host as loopback adapter IPv4", - "system_hostname": "datanerd-01", - "db_hostname": "127.0.0.1", - "product": "Postgres", - "port": 5252, - "expected_instance_metric": "Datastore/instance/Postgres/datanerd-01/5252" - }, - { - "name": "instance metric uses system hostname when db reports host as loopback adapter IPv6", - "system_hostname": "datanerd-01", - "db_hostname": "0:0:0:0:0:0:0:1", - "product": "Postgres", - "port": 5252, - "expected_instance_metric": "Datastore/instance/Postgres/datanerd-01/5252" - }, - { - "name": "instance metric uses system hostname when db reports host as loopback adapter IPv6 shorthand", - "system_hostname": "datanerd-01", - "db_hostname": "::1", - "product": "Postgres", - "port": 5252, - "expected_instance_metric": "Datastore/instance/Postgres/datanerd-01/5252" - }, - { - "name": "instance metric uses system hostname when db reports default host IPv6 shorthand", - "system_hostname": "datanerd-01", - "db_hostname": "::", - "product": "MySQL", - "port": 5757, - "expected_instance_metric": "Datastore/instance/MySQL/datanerd-01/5757" - }, - { - "name": "instance metric uses db host when not local", - "system_hostname": "datanerd-01", - "db_hostname": "accounts-db", - "product": "MySQL", - "port": 8420, - "expected_instance_metric": "Datastore/instance/MySQL/accounts-db/8420" - }, - { - "name": "instance metric uses unix socket path if provided", - "system_hostname": "datanerd-01", - "db_hostname": "localhost", - "product": "MySQL", - "unix_socket": "/var/mysql/mysql.sock", - "expected_instance_metric": "Datastore/instance/MySQL/datanerd-01//var/mysql/mysql.sock" - }, - { - "name": "instance metric with ip v6 host", - "system_hostname": "datanerd-01", - "db_hostname": "2001:0DB8:AC10:FE01:0000:0000:0000:0000", - "product": "Postgres", - "port": 5432, - "expected_instance_metric": "Datastore/instance/Postgres/2001:0DB8:AC10:FE01:0000:0000:0000:0000/5432" - }, - { - "name": "instance metric for filesystem database", - "system_hostname": "datanerd-01", - "product": "SQLite", - "database_path": "/db/all.sqlite3", - "expected_instance_metric": "Datastore/instance/SQLite/datanerd-01//db/all.sqlite3" - } -] diff --git a/internal/crossagent/cross_agent_tests/distributed_tracing/distributed_tracing.json b/internal/crossagent/cross_agent_tests/distributed_tracing/distributed_tracing.json deleted file mode 100644 index 5487a3fa4..000000000 --- a/internal/crossagent/cross_agent_tests/distributed_tracing/distributed_tracing.json +++ /dev/null @@ -1,1281 +0,0 @@ -[ - { - "test_name": "accept_payload", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "id": "7d3efb1b173fecfa", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction", "Span"], - "common":{ - "exact": { - "traceId": "d6b4ba0c3a712ca", - "priority": 1.234567, - "sampled": true - }, - "expected": ["guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "parentId": "e8b91a159289ff74", - "parentSpanId": "7d3efb1b173fecfa" - }, - "expected": ["parent.transportDuration"] - }, - "Span": { - "exact": { - "parentId": "7d3efb1b173fecfa" - }, - "expected": ["transactionId"], - "unexpected": ["parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType"] - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1] - ] - }, - { - "test_name": "multiple_accept_calls", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 0.123456, - "sa": false, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - }, - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2097282", - "tx": "23cb0b7a48407caf", - "id": "b4a07f08064ee8f9", - "pr": 1.234567, - "sa": true, - "ti": 1530549828110, - "tr": "c3e4882169ac3509", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "traceId": "d6b4ba0c3a712ca", - "priority": 0.123456, - "sampled": false - }, - "expected": ["parent.transportDuration", "guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parentId": "e8b91a159289ff74" - } - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1], - ["Supportability/DistributedTrace/AcceptPayload/Ignored/Multiple", 1] - ] - }, - { - "test_name": "payload_with_sampled_false", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 0.123456, - "sa": false, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "traceId": "d6b4ba0c3a712ca", - "priority": 0.123456, - "sampled": false - }, - "expected": ["parent.transportDuration", "guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parentId": "e8b91a159289ff74" - } - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1] - ] - }, - { - "test_name": "spans_disabled_in_parent", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction", "Span"], - "common":{ - "exact": { - "traceId": "d6b4ba0c3a712ca", - "priority": 1.234567, - "sampled": true - }, - "expected": ["guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "parentId": "e8b91a159289ff74" - }, - "expected": ["parent.transportDuration"], - "unexpected": ["parentSpanId"] - }, - "Span": { - "expected": ["transactionId"], - "unexpected": ["parentId", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType"] - - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1] - ] - }, - { - "test_name": "spans_disabled_in_child", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": false, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "id": "7d3efb1b173fecfa", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "outbound_payloads": [ - { - "exact": { - "v": [0, 1], - "d.ac": "33", - "d.pr": 1.234567, - "d.sa": true, - "d.tr": "d6b4ba0c3a712ca", - "d.ty": "App" - }, - "expected": ["d.ap", "d.tx", "d.ti"], - "unexpected": ["d.tk", "d.id"] - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "traceId": "d6b4ba0c3a712ca", - "priority": 1.234567, - "sampled": true - }, - "expected": ["parent.transportDuration", "guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parentId": "e8b91a159289ff74", - "parentSpanId": "7d3efb1b173fecfa" - } - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1] - ] - }, - { - "test_name": "exception", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": true, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "id": "7d3efb1b173fecfa", - "tx": "e8b91a159289ff74", - "pr": 1.001, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction", "TransactionError", "Span"], - "common":{ - "exact": { - "traceId": "d6b4ba0c3a712ca", - "priority": 1.001, - "sampled": true - }, - "expected": ["guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "error": true, - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "parentId": "e8b91a159289ff74", - "parentSpanId": "7d3efb1b173fecfa" - }, - "expected": ["parent.transportDuration"] - }, - "Span": { - "exact": { - "parentId": "7d3efb1b173fecfa" - }, - "expected": ["transactionId"], - "unexpected": ["parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType"] - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["ErrorsByCaller/App/33/2827902/HTTP/all", 1], - ["ErrorsByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1] - ] - }, - { - "test_name": "background_transaction", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": false, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "id": "7d3efb1b173fecfa", - "tx": "e8b91a159289ff74", - "pr": 0.234567, - "sa": false, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "traceId": "d6b4ba0c3a712ca", - "priority": 0.234567, - "sampled": false - }, - "expected": ["parent.transportDuration", "guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parentId": "e8b91a159289ff74", - "parentSpanId": "7d3efb1b173fecfa" - } - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allOther", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allOther", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1] - ] - }, - { - "test_name": "payload_from_mobile_caller", - "comment": "the transaction must be marked sampled=true so a Span event is created", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": true, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "id": "7d3efb1b173fecfa", - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "Mobile" - } - } - ], - "intrinsics": { - "target_events": ["Transaction", "Span"], - "common":{ - "exact": { - "traceId": "d6b4ba0c3a712ca", - "sampled": true - }, - "expected": ["guid", "priority"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parent.type": "Mobile", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "parentSpanId": "7d3efb1b173fecfa" - }, - "expected": ["parent.transportDuration"], - "unexpected": ["parentId"] - }, - "Span": { - "exact": { - "parentId": "7d3efb1b173fecfa" - }, - "expected": ["transactionId"], - "unexpected": ["parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType"] - } - }, - "expected_metrics": [ - ["DurationByCaller/Mobile/33/2827902/HTTP/all", 1], - ["DurationByCaller/Mobile/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/Mobile/33/2827902/HTTP/all", 1], - ["TransportDuration/Mobile/33/2827902/HTTP/allWeb", 1] - ] - }, - { - "test_name": "lowercase_known_transport_is_unknown", - "comment": "beware the casing!", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "kafka", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 0.123456, - "sa": false, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "Unknown", - "traceId": "d6b4ba0c3a712ca", - "priority": 0.123456, - "sampled": false - }, - "expected": ["parent.transportDuration", "guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parentId": "e8b91a159289ff74" - } - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/Unknown/all", 1], - ["DurationByCaller/App/33/2827902/Unknown/allWeb", 1], - ["TransportDuration/App/33/2827902/Unknown/all", 1], - ["TransportDuration/App/33/2827902/Unknown/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1] - ] - }, - { - "test_name": "create_payload", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "id": "7d3efb1b173fecfa", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "outbound_payloads": [ - { - "exact": { - "v": [0, 1], - "d.ac": "33", - "d.pr": 1.234567, - "d.sa": true, - "d.tr": "d6b4ba0c3a712ca", - "d.ty": "App" - }, - "expected": ["d.ap", "d.tx", "d.id", "d.ti"], - "unexpected": ["d.tk"] - } - ], - "intrinsics": { - "target_events": ["Transaction", "Span"], - "common":{ - "exact": { - "traceId": "d6b4ba0c3a712ca", - "priority": 1.234567, - "sampled": true - }, - "expected": ["guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "parentId": "e8b91a159289ff74", - "parentSpanId": "7d3efb1b173fecfa" - }, - "expected": ["parent.transportDuration"] - }, - "Span": { - "exact": { - "parentId": "7d3efb1b173fecfa" - }, - "expected": ["transactionId"], - "unexpected": ["parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType"] - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1], - ["Supportability/DistributedTrace/CreatePayload/Success", 1] - ] - }, - { - "test_name": "multiple_create_calls", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "id": "7d3efb1b173fecfa", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "tk": "33", - "ty": "App" - } - } - ], - "outbound_payloads": [ - { - "exact": { - "v": [0, 1], - "d.ac": "33", - "d.pr": 1.234567, - "d.sa": true, - "d.tr": "d6b4ba0c3a712ca", - "d.ty": "App" - }, - "expected": ["d.ap", "d.tx", "d.id", "d.ti"], - "unexpected": ["d.tk"] - }, - { - "exact": { - "v": [0, 1], - "d.ac": "33", - "d.pr": 1.234567, - "d.sa": true, - "d.tr": "d6b4ba0c3a712ca", - "d.ty": "App" - }, - "expected": ["d.ap", "d.tx", "d.id", "d.ti"], - "unexpected": ["d.tk"] - } - ], - "intrinsics": { - "target_events": ["Transaction", "Span"], - "common":{ - "exact": { - "traceId": "d6b4ba0c3a712ca", - "priority": 1.234567, - "sampled": true - }, - "expected": ["guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "parentId": "e8b91a159289ff74", - "parentSpanId": "7d3efb1b173fecfa" - }, - "expected": ["parent.transportDuration"] - }, - "Span": { - "exact": { - "parentId": "7d3efb1b173fecfa" - }, - "expected": ["transactionId"], - "unexpected": ["parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType"] - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1], - ["Supportability/DistributedTrace/CreatePayload/Success", 2] - ] - }, - { - "test_name": "payload_from_trusted_partnership_account", - "trusted_account_key": "44", - "account_id": "11", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 0.123456, - "sa": false, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "tk": "44", - "ty": "App" - } - } - ], - "outbound_payloads": [ - { - "exact": { - "v": [0, 1], - "d.ac": "11", - "d.pr": 0.123456, - "d.sa": false, - "d.tr": "d6b4ba0c3a712ca", - "d.tk": "44", - "d.ty": "App" - }, - "expected": ["d.ap", "d.tx", "d.ti"], - "unexpected": ["d.id"] - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "traceId": "d6b4ba0c3a712ca", - "priority": 0.123456, - "sampled": false - }, - "expected": ["parent.transportDuration", "guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parentId": "e8b91a159289ff74" - } - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1], - ["Supportability/DistributedTrace/CreatePayload/Success", 1] - ] - }, - { - "test_name": "payload_has_larger_minor_version", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 2], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 0.123456, - "sa": false, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App", - "xx": "this is an unknown field!" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "exact": { - "parent.type": "App", - "parent.app": "2827902", - "parent.account": "33", - "parent.transportType": "HTTP", - "traceId": "d6b4ba0c3a712ca", - "priority": 0.123456, - "sampled": false - }, - "expected": ["parent.transportDuration", "guid"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes"] - }, - "Transaction": { - "exact": { - "parentId": "e8b91a159289ff74" - } - } - }, - "expected_metrics": [ - ["DurationByCaller/App/33/2827902/HTTP/all", 1], - ["DurationByCaller/App/33/2827902/HTTP/allWeb", 1], - ["TransportDuration/App/33/2827902/HTTP/all", 1], - ["TransportDuration/App/33/2827902/HTTP/allWeb", 1], - ["Supportability/DistributedTrace/AcceptPayload/Success", 1] - ] - }, - { - "test_name": "payload_with_untrusted_key", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "11", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 0.123456, - "sa": false, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "tk": "44", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/Ignored/UntrustedAccount", 1] - ] - }, - { - "test_name": "payload_from_untrusted_account", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "44", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 0.123456, - "sa": false, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/Ignored/UntrustedAccount", 1] - ] - }, - { - "test_name": "payload_has_larger_major_version", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [1, 0], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/Ignored/MajorVersion", 1] - ] - }, - { - "test_name": "null_payload", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": null, - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/Ignored/Null", 1] - ] - }, - { - "test_name": "payload_missing_version", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/ParseException", 1] - ] - }, - { - "test_name": "payload_missing_data", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1] - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/ParseException", 1] - ] - }, - { - "test_name": "payload_missing_account", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/ParseException", 1] - ] - }, - { - "test_name": "payload_missing_application", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/ParseException", 1] - ] - }, - { - "test_name": "payload_missing_type", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/ParseException", 1] - ] - }, - { - "test_name": "payload_missing_transactionId_or_guid", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/ParseException", 1] - ] - }, - { - "test_name": "payload_missing_traceId", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "ti": 1518469636035, - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/ParseException", 1] - ] - }, - { - "test_name": "payload_missing_timestamp", - "trusted_account_key": "33", - "account_id": "33", - "web_transaction": true, - "raises_exception": false, - "force_sampled_true": false, - "span_events_enabled": true, - "major_version": 0, - "minor_version": 1, - "transport_type": "HTTP", - "inbound_payloads": [ - { - "v": [0, 1], - "d": { - "ac": "33", - "ap": "2827902", - "tx": "e8b91a159289ff74", - "pr": 1.234567, - "sa": true, - "tr": "d6b4ba0c3a712ca", - "ty": "App" - } - } - ], - "intrinsics": { - "target_events": ["Transaction"], - "common":{ - "expected": ["guid", "traceId", "priority", "sampled"], - "unexpected": ["grandparentId", "cross_process_id", "nr.tripId", "nr.pathHash", "nr.referringPathHash", "nr.guid", "nr.referringTransactionGuid", "nr.alternatePathHashes", "parent.transportDuration", "parent.type", "parent.app", "parent.account", "parent.transportType", "parentId"] - } - }, - "expected_metrics": [ - ["Supportability/DistributedTrace/AcceptPayload/ParseException", 1] - ] - } -] diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/README.md b/internal/crossagent/cross_agent_tests/docker_container_id/README.md deleted file mode 100644 index 0b69ba17f..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/README.md +++ /dev/null @@ -1,6 +0,0 @@ -These tests cover parsing of Docker container IDs on Linux hosts out of -`/proc/self/cgroup` (or `/proc//cgroup` more generally). - -The `cases.json` file lists each filename in this directory containing -example `/proc/self/cgroup` content, and the expected Docker container ID that -should be parsed from that file. diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/cases.json b/internal/crossagent/cross_agent_tests/docker_container_id/cases.json deleted file mode 100644 index 18c823cda..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/cases.json +++ /dev/null @@ -1,86 +0,0 @@ -[ - { - "filename": "docker-0.9.1.txt", - "containerId": "f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee", - "expectedMetrics": null - }, - { - "filename": "docker-1.0.0.txt", - "containerId": "3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a782", - "expectedMetrics": null - }, - { - "filename": "docker-custom-prefix.txt", - "containerId": "e6aaf072b17c345d900987ce04e37031d198b02314f8636df2c0edf6538c08c7", - "expectedMetrics": null - }, - { - "filename": "docker-too-long.txt", - "containerId": null, - "expectedMetrics": null - }, - { - "filename": "docker-1.1.2-lxc-driver.txt", - "containerId": "cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159", - "expectedMetrics": null - }, - { - "filename": "docker-1.1.2-native-driver-fs.txt", - "containerId": "2a4f870e24a3b52eb9fe7f3e02858c31855e213e568cfa6c76cb046ffa5b8a28", - "expectedMetrics": null - }, - { - "filename": "docker-1.1.2-native-driver-systemd.txt", - "containerId": "67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f", - "expectedMetrics": null - }, - { - "filename": "docker-1.3.txt", - "containerId": "47cbd16b77c50cbf71401c069cd2189f0e659af17d5a2daca3bddf59d8a870b2", - "expectedMetrics": null - }, - { - "filename": "heroku.txt", - "containerId": null, - "expectedMetrics": null - }, - { - "filename": "ubuntu-14.04-no-container.txt", - "containerId": null, - "expectedMetrics": null - }, - { - "filename": "ubuntu-14.04-lxc-container.txt", - "containerId": null, - "expectedMetrics": null - }, - { - "filename": "ubuntu-14.10-no-container.txt", - "containerId": null, - "expectedMetrics": null - }, - { - "filename": "empty.txt", - "containerId": null, - "expectedMetrics": null - }, - { - "filename": "invalid-characters.txt", - "containerId": null, - "expectedMetrics": null - }, - { - "filename": "invalid-length.txt", - "containerId": null, - "expectedMetrics": { - "Supportability/utilization/docker/error": { - "callCount": 1 - } - } - }, - { - "filename": "no_cpu_subsystem.txt", - "containerId": null, - "expectedMetrics": null - } -] diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/docker-0.9.1.txt b/internal/crossagent/cross_agent_tests/docker_container_id/docker-0.9.1.txt deleted file mode 100644 index dfc8b8067..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/docker-0.9.1.txt +++ /dev/null @@ -1,10 +0,0 @@ -11:hugetlb:/ -10:perf_event:/ -9:blkio:/ -8:freezer:/ -7:devices:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -6:memory:/ -5:cpuacct:/ -4:cpu:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -3:cpuset:/ -2:name=systemd:/ \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.0.0.txt b/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.0.0.txt deleted file mode 100644 index 0e85d10ce..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.0.0.txt +++ /dev/null @@ -1,10 +0,0 @@ -11:hugetlb:/ -10:perf_event:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a782 -9:blkio:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a782 -8:freezer:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a782 -7:devices:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a782 -6:memory:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a782 -5:cpuacct:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a782 -4:cpu:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a782 -3:cpuset:/ -2:name=systemd:/ diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-lxc-driver.txt b/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-lxc-driver.txt deleted file mode 100644 index f2c3d3dcc..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-lxc-driver.txt +++ /dev/null @@ -1,10 +0,0 @@ -11:hugetlb:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 -10:perf_event:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 -9:blkio:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 -8:freezer:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 -7:name=systemd:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 -6:devices:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 -5:memory:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 -4:cpuacct:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 -3:cpu:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 -2:cpuset:/lxc/cb8c113e5f3cf8332f5231f8154adc429ea910f7c29995372de4f571c55d3159 \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-native-driver-fs.txt b/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-native-driver-fs.txt deleted file mode 100644 index e29f420da..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-native-driver-fs.txt +++ /dev/null @@ -1,10 +0,0 @@ -11:hugetlb:/ -10:perf_event:/docker/2a4f870e24a3b52eb9fe7f3e02858c31855e213e568cfa6c76cb046ffa5b8a28 -9:blkio:/docker/2a4f870e24a3b52eb9fe7f3e02858c31855e213e568cfa6c76cb046ffa5b8a28 -8:freezer:/docker/2a4f870e24a3b52eb9fe7f3e02858c31855e213e568cfa6c76cb046ffa5b8a28 -7:name=systemd:/ -6:devices:/docker/2a4f870e24a3b52eb9fe7f3e02858c31855e213e568cfa6c76cb046ffa5b8a28 -5:memory:/docker/2a4f870e24a3b52eb9fe7f3e02858c31855e213e568cfa6c76cb046ffa5b8a28 -4:cpuacct:/docker/2a4f870e24a3b52eb9fe7f3e02858c31855e213e568cfa6c76cb046ffa5b8a28 -3:cpu:/docker/2a4f870e24a3b52eb9fe7f3e02858c31855e213e568cfa6c76cb046ffa5b8a28 -2:cpuset:/ \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-native-driver-systemd.txt b/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-native-driver-systemd.txt deleted file mode 100644 index 7e7e1ce0d..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.1.2-native-driver-systemd.txt +++ /dev/null @@ -1,10 +0,0 @@ -10:hugetlb:/ -9:perf_event:/ -8:blkio:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope -7:net_cls:/ -6:freezer:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope -5:devices:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope -4:memory:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope -3:cpuacct,cpu:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope -2:cpuset:/ -1:name=systemd:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.3.txt b/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.3.txt deleted file mode 100644 index 1ccd44343..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/docker-1.3.txt +++ /dev/null @@ -1,9 +0,0 @@ -9:perf_event:/docker/47cbd16b77c50cbf71401c069cd2189f0e659af17d5a2daca3bddf59d8a870b2 -8:blkio:/docker/47cbd16b77c50cbf71401c069cd2189f0e659af17d5a2daca3bddf59d8a870b2 -7:net_cls:/ -6:freezer:/docker/47cbd16b77c50cbf71401c069cd2189f0e659af17d5a2daca3bddf59d8a870b2 -5:devices:/docker/47cbd16b77c50cbf71401c069cd2189f0e659af17d5a2daca3bddf59d8a870b2 -4:memory:/docker/47cbd16b77c50cbf71401c069cd2189f0e659af17d5a2daca3bddf59d8a870b2 -3:cpuacct:/docker/47cbd16b77c50cbf71401c069cd2189f0e659af17d5a2daca3bddf59d8a870b2 -2:cpu:/docker/47cbd16b77c50cbf71401c069cd2189f0e659af17d5a2daca3bddf59d8a870b2 -1:cpuset:/ diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/docker-custom-prefix.txt b/internal/crossagent/cross_agent_tests/docker_container_id/docker-custom-prefix.txt deleted file mode 100644 index 75a1aa4ce..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/docker-custom-prefix.txt +++ /dev/null @@ -1,10 +0,0 @@ -11:hugetlb:/ -10:perf_event:/custom-foobar/e6aaf072b17c345d900987ce04e37031d198b02314f8636df2c0edf6538c08c7 -9:blkio:/custom-foobar/e6aaf072b17c345d900987ce04e37031d198b02314f8636df2c0edf6538c08c7 -8:freezer:/custom-foobar/e6aaf072b17c345d900987ce04e37031d198b02314f8636df2c0edf6538c08c7 -7:devices:/custom-foobar/e6aaf072b17c345d900987ce04e37031d198b02314f8636df2c0edf6538c08c7 -6:memory:/custom-foobar/e6aaf072b17c345d900987ce04e37031d198b02314f8636df2c0edf6538c08c7 -5:cpuacct:/custom-foobar/e6aaf072b17c345d900987ce04e37031d198b02314f8636df2c0edf6538c08c7 -4:cpu:/custom-foobar/e6aaf072b17c345d900987ce04e37031d198b02314f8636df2c0edf6538c08c7 -3:cpuset:/ -2:name=systemd:/ diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/docker-too-long.txt b/internal/crossagent/cross_agent_tests/docker_container_id/docker-too-long.txt deleted file mode 100644 index 6eb1dcfb9..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/docker-too-long.txt +++ /dev/null @@ -1,10 +0,0 @@ -11:hugetlb:/ -10:perf_event:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821 -9:blkio:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821 -8:freezer:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821 -7:devices:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821 -6:memory:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821 -5:cpuacct:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821 -4:cpu:/docker/3ccfa00432798ff38f85839de1e396f771b4acbe9f4ddea0a761c39b9790a7821 -3:cpuset:/ -2:name=systemd:/ diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/empty.txt b/internal/crossagent/cross_agent_tests/docker_container_id/empty.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/heroku.txt b/internal/crossagent/cross_agent_tests/docker_container_id/heroku.txt deleted file mode 100644 index 7a1ae0d78..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/heroku.txt +++ /dev/null @@ -1 +0,0 @@ -1:hugetlb,perf_event,blkio,freezer,devices,memory,cpuacct,cpu,cpuset:/lxc/b6d196c1-50f2-4949-abdb-5d4909864487 diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/invalid-characters.txt b/internal/crossagent/cross_agent_tests/docker_container_id/invalid-characters.txt deleted file mode 100644 index 4ff2f23f0..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/invalid-characters.txt +++ /dev/null @@ -1,9 +0,0 @@ -9:perf_event:/docker/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55 -8:blkio:/docker/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55 -7:net_cls:/ -6:freezer:/docker/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55 -5:devices:/docker/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55 -4:memory:/docker/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55 -3:cpuacct:/docker/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55 -2:cpu:/docker/WRONGINCORRECTINVALIDCHARSERRONEOUSBADPHONYBROKEN2TERRIBLENOPE55 -1:cpuset:/ diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/invalid-length.txt b/internal/crossagent/cross_agent_tests/docker_container_id/invalid-length.txt deleted file mode 100644 index 8166b21fa..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/invalid-length.txt +++ /dev/null @@ -1,9 +0,0 @@ -9:perf_event:/docker/47cbd16b77c5 -8:blkio:/docker/47cbd16b77c5 -7:net_cls:/ -6:freezer:/docker/47cbd16b77c5 -5:devices:/docker/47cbd16b77c5 -4:memory:/docker/47cbd16b77c5 -3:cpuacct:/docker/47cbd16b77c5 -2:cpu:/docker/47cbd16b77c5 -1:cpuset:/ diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/no_cpu_subsystem.txt b/internal/crossagent/cross_agent_tests/docker_container_id/no_cpu_subsystem.txt deleted file mode 100644 index 717b38bf7..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/no_cpu_subsystem.txt +++ /dev/null @@ -1,10 +0,0 @@ -11:hugetlb:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -10:perf_event:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -9:blkio:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -8:freezer:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -7:devices:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -6:memory:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -5:cpuacct:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -4:cpu:/ -3:cpuset:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee -2:name=systemd:/docker/f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.04-lxc-container.txt b/internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.04-lxc-container.txt deleted file mode 100644 index 8c5b635a5..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.04-lxc-container.txt +++ /dev/null @@ -1,10 +0,0 @@ -11:hugetlb:/lxc/p1 -10:perf_event:/lxc/p1 -9:blkio:/lxc/p1 -8:freezer:/lxc/p1 -7:devices:/lxc/p1 -6:memory:/lxc/p1 -5:cpuacct:/lxc/p1 -4:cpu:/lxc/p1 -3:cpuset:/lxc/p1 -2:name=systemd:/user/1000.user/1.session \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.04-no-container.txt b/internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.04-no-container.txt deleted file mode 100644 index 4439bc556..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.04-no-container.txt +++ /dev/null @@ -1,10 +0,0 @@ -11:hugetlb:/user/1000.user/2.session -10:perf_event:/user/1000.user/2.session -9:blkio:/user/1000.user/2.session -8:freezer:/user/1000.user/2.session -7:devices:/user/1000.user/2.session -6:memory:/user/1000.user/2.session -5:cpuacct:/user/1000.user/2.session -4:cpu:/user/1000.user/2.session -3:cpuset:/user/1000.user/2.session -2:name=systemd:/user/1000.user/2.session \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.10-no-container.txt b/internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.10-no-container.txt deleted file mode 100644 index fc9873642..000000000 --- a/internal/crossagent/cross_agent_tests/docker_container_id/ubuntu-14.10-no-container.txt +++ /dev/null @@ -1,10 +0,0 @@ -10:hugetlb:/ -9:perf_event:/ -8:blkio:/ -7:net_cls,net_prio:/ -6:freezer:/ -5:devices:/ -4:memory:/ -3:cpu,cpuacct:/ -2:cpuset:/ -1:name=systemd:/user.slice/user-1000.slice/session-2.scope \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/labels.json b/internal/crossagent/cross_agent_tests/labels.json deleted file mode 100644 index fc2cb6cb5..000000000 --- a/internal/crossagent/cross_agent_tests/labels.json +++ /dev/null @@ -1,195 +0,0 @@ -[ - { - "name": "empty", - "labelString": "", - "warning": false, - "expected": [] - }, - { - "name": "multiple_values", - "labelString": "Data Center: East;Data Center :West; Server : North;Server:South; ", - "warning": false, - "expected": [ - { "label_type": "Data Center", "label_value": "West" }, - { "label_type": "Server", "label_value": "South" } - ] - }, - { - "name": "multiple_labels_with_leading_and_trailing_whitespaces", - "labelString": " Data Center : East Coast ; Deployment Flavor : Integration Environment ", - "warning": false, - "expected": [ - { "label_type": "Data Center", "label_value": "East Coast" }, - { "label_type": "Deployment Flavor", "label_value": "Integration Environment" } - ] - }, - { - "name": "single", - "labelString": "Server:East", - "warning": false, - "expected": [ { "label_type": "Server", "label_value": "East" } ] - }, - { - "name": "single_label_with_leading_and_trailing_whitespaces", - "labelString": " Data Center : East Coast ", - "warning": false, - "expected": [ { "label_type": "Data Center", "label_value": "East Coast" } ] - }, - { - "name": "single_trailing_semicolon", - "labelString": "Server:East;", - "warning": false, - "expected": [ { "label_type": "Server", "label_value": "East" } ] - }, - { - "name": "pair", - "labelString": "Data Center:Primary;Server:East", - "warning": false, - "expected": [ - { "label_type": "Data Center", "label_value": "Primary" }, - { "label_type": "Server", "label_value": "East" } - ] - }, - { - "name": "truncation", - "labelString": "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK:VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV", - "warning": true, - "expected": [ { - "label_type": "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK", - "label_value": "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" - } ] - }, - { - "name": "single_label_key_to_be_truncated_with_leading_and_trailing_whitespaces", - "labelString": " 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345TTTTT :value", - "warning": true, - "expected": [ - { "label_type": "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345", "label_value": "value" } - ] - }, - { - "name": "single_label_value_to_be_truncated_with_leading_and_trailing_whitespaces", - "labelString": "key: 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345TTTTT ", - "warning": true, - "expected": [ - { "label_type": "key", "label_value": "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345" } - ] - }, - { - "name": "utf8", - "labelString": "kéÿ:vãlüê", - "warning": false, - "expected": [ - { "label_type": "kéÿ", "label_value": "vãlüê" } - ] - }, - { - "name": "failed_no_delimiters", - "labelString": "Server", - "warning": true, - "expected": [] - }, - { - "name": "failed_no_delimiter", - "labelString": "ServerNorth;", - "warning": true, - "expected": [] - }, - { - "name": "failed_too_many_delimiters", - "labelString": "Server:North:South;", - "warning": true, - "expected": [] - }, - { - "name": "failed_no_value", - "labelString": "Server: ", - "warning": true, - "expected": [] - }, - { - "name": "failed_no_key", - "labelString": ":North", - "warning": true, - "expected": [] - }, - { - "name": "failed_no_delimiter_in_later_pair", - "labelString": "Server:North;South;", - "warning": true, - "expected": [] - }, - { - "name": "so_many_labels", - "labelString": "0:0;1:1;2:2;3:3;4:4;5:5;6:6;7:7;8:8;9:9;10:10;11:11;12:12;13:13;14:14;15:15;16:16;17:17;18:18;19:19;20:20;21:21;22:22;23:23;24:24;25:25;26:26;27:27;28:28;29:29;30:30;31:31;32:32;33:33;34:34;35:35;36:36;37:37;38:38;39:39;40:40;41:41;42:42;43:43;44:44;45:45;46:46;47:47;48:48;49:49;50:50;51:51;52:52;53:53;54:54;55:55;56:56;57:57;58:58;59:59;60:60;61:61;62:62;63:63;64:64;65:65;66:66;67:67;68:68;69:69;70:70;71:71;72:72;73:73;74:74;75:75;76:76;77:77;78:78;79:79;80:80;81:81;82:82;83:83;84:84;85:85;86:86;87:87;88:88;89:89;90:90;91:91;92:92;93:93;94:94;95:95;96:96;97:97;98:98;99:99;", - "warning": true, - "expected": [ - { "label_type": "0", "label_value": "0" }, { "label_type": "1", "label_value": "1" }, { "label_type": "2", "label_value": "2" }, { "label_type": "3", "label_value": "3" }, { "label_type": "4", "label_value": "4" }, - { "label_type": "5", "label_value": "5" }, { "label_type": "6", "label_value": "6" }, { "label_type": "7", "label_value": "7" }, { "label_type": "8", "label_value": "8" }, { "label_type": "9", "label_value": "9" }, - { "label_type": "10", "label_value": "10" }, { "label_type": "11", "label_value": "11" }, { "label_type": "12", "label_value": "12" }, { "label_type": "13", "label_value": "13" }, { "label_type": "14", "label_value": "14" }, - { "label_type": "15", "label_value": "15" }, { "label_type": "16", "label_value": "16" }, { "label_type": "17", "label_value": "17" }, { "label_type": "18", "label_value": "18" }, { "label_type": "19", "label_value": "19" }, - { "label_type": "20", "label_value": "20" }, { "label_type": "21", "label_value": "21" }, { "label_type": "22", "label_value": "22" }, { "label_type": "23", "label_value": "23" }, { "label_type": "24", "label_value": "24" }, - { "label_type": "25", "label_value": "25" }, { "label_type": "26", "label_value": "26" }, { "label_type": "27", "label_value": "27" }, { "label_type": "28", "label_value": "28" }, { "label_type": "29", "label_value": "29" }, - { "label_type": "30", "label_value": "30" }, { "label_type": "31", "label_value": "31" }, { "label_type": "32", "label_value": "32" }, { "label_type": "33", "label_value": "33" }, { "label_type": "34", "label_value": "34" }, - { "label_type": "35", "label_value": "35" }, { "label_type": "36", "label_value": "36" }, { "label_type": "37", "label_value": "37" }, { "label_type": "38", "label_value": "38" }, { "label_type": "39", "label_value": "39" }, - { "label_type": "40", "label_value": "40" }, { "label_type": "41", "label_value": "41" }, { "label_type": "42", "label_value": "42" }, { "label_type": "43", "label_value": "43" }, { "label_type": "44", "label_value": "44" }, - { "label_type": "45", "label_value": "45" }, { "label_type": "46", "label_value": "46" }, { "label_type": "47", "label_value": "47" }, { "label_type": "48", "label_value": "48" }, { "label_type": "49", "label_value": "49" }, - { "label_type": "50", "label_value": "50" }, { "label_type": "51", "label_value": "51" }, { "label_type": "52", "label_value": "52" }, { "label_type": "53", "label_value": "53" }, { "label_type": "54", "label_value": "54" }, - { "label_type": "55", "label_value": "55" }, { "label_type": "56", "label_value": "56" }, { "label_type": "57", "label_value": "57" }, { "label_type": "58", "label_value": "58" }, { "label_type": "59", "label_value": "59" }, - { "label_type": "60", "label_value": "60" }, { "label_type": "61", "label_value": "61" }, { "label_type": "62", "label_value": "62" }, { "label_type": "63", "label_value": "63" } ] - }, - { - "name": "trailing_semicolons", - "labelString": "foo:bar;;", - "warning": false, - "expected": [ { "label_type": "foo", "label_value": "bar" } ] - }, - { - "name": "leading_semicolons", - "labelString": ";;foo:bar", - "warning": false, - "expected": [ { "label_type": "foo", "label_value": "bar" } ] - }, - { - "name": "empty_label", - "labelString": "foo:bar;;zip:zap", - "warning": true, - "expected": [] - }, - { - "name": "trailing_colons", - "labelString": "foo:bar;:", - "warning": true, - "expected": [] - }, - { - "name": "leading_colons", - "labelString": ":;foo:bar", - "warning": true, - "expected": [] - }, - { - "name": "empty_pair", - "labelString": " : ", - "warning": true, - "expected": [] - }, - { - "name": "empty_pair_in_middle_of_string", - "labelString": "foo:bar; : ;zip:zap", - "warning": true, - "expected": [] - }, - { - "name": "long_multibyte_utf8", - "labelString": "foo:€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€", - "warning": true, - "expected": [ { "label_type": "foo", "label_value": "€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€" } ] - }, - { - "name": "long_4byte_utf8", - "labelString": "foo:𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆", - "warning": true, - "expected": [ { "label_type": "foo", "label_value": "𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆𝌆"}] - } -] diff --git a/internal/crossagent/cross_agent_tests/language_agents_security_policies.json b/internal/crossagent/cross_agent_tests/language_agents_security_policies.json deleted file mode 100644 index cd1b5c75b..000000000 --- a/internal/crossagent/cross_agent_tests/language_agents_security_policies.json +++ /dev/null @@ -1,269 +0,0 @@ -[{ - "name": "should respect record_sql policy", - "required_features": ["record_sql"], - "starting_policy_settings": { - "record_sql": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": false, "required": false, "position": 0}, - "attributes_include": {"enabled": true, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": true, "required": false, "position": 2}, - "custom_events": {"enabled": true, "required": false, "position": 3}, - "custom_parameters": {"enabled": true, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": true, "required": false, "position": 5}, - "message_parameters": {"enabled": true, "required": false, "position": 6}, - "job_arguments": {"enabled": true, "required": false, "position": 7} - }, - "expected_connect_policies": { - "record_sql": {"enabled": false} - }, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "record_sql": {"enabled": false} - }, - "should_log": false, - "should_shutdown": false -}, { - "name": "should respect attributes_include policy", - "required_features": ["attributes_include"], - "starting_policy_settings": { - "attributes_include": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": true, "required": false, "position": 0}, - "attributes_include": {"enabled": false, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": true, "required": false, "position": 2}, - "custom_events": {"enabled": true, "required": false, "position": 3}, - "custom_parameters": {"enabled": true, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": true, "required": false, "position": 5}, - "message_parameters": {"enabled": true, "required": false, "position": 6}, - "job_arguments": {"enabled": true, "required": false, "position": 7} - }, - "expected_connect_policies": { - "attributes_include": {"enabled": false} - }, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "attributes_include": {"enabled": false} - }, - "should_log": false, - "should_shutdown": false -}, { - "name": "should respect allow_raw_exception_messages policy and more secure local setting", - "required_features": ["allow_raw_exception_messages"], - "starting_policy_settings": { - "allow_raw_exception_messages": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": true, "required": false, "position": 0}, - "attributes_include": {"enabled": true, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": false, "required": false, "position": 2}, - "custom_events": {"enabled": true, "required": false, "position": 3}, - "custom_parameters": {"enabled": true, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": true, "required": false, "position": 5}, - "message_parameters": {"enabled": true, "required": false, "position": 6}, - "job_arguments": {"enabled": true, "required": false, "position": 7} - }, - "expected_connect_policies": { - "allow_raw_exception_messages": {"enabled": false} - }, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "allow_raw_exception_messages": {"enabled": false} - }, - "should_log": false, - "should_shutdown": false -}, { - "name": "should respect custom_events policy", - "required_features": ["custom_events"], - "starting_policy_settings": { - "custom_events": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": true, "required": false, "position": 0}, - "attributes_include": {"enabled": true, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": true, "required": false, "position": 2}, - "custom_events": {"enabled": false, "required": false, "position": 3}, - "custom_parameters": {"enabled": true, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": true, "required": false, "position": 5}, - "message_parameters": {"enabled": true, "required": false, "position": 6}, - "job_arguments": {"enabled": true, "required": false, "position": 7} - }, - "expected_connect_policies": { - "custom_events": {"enabled": false} - }, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "custom_events": {"enabled": false} - }, - "should_log": false, - "should_shutdown": false -}, { - "name": "should respect custom_parameters policy", - "required_features": ["custom_parameters"], - "starting_policy_settings": { - "custom_parameters": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": true, "required": false, "position": 0}, - "attributes_include": {"enabled": true, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": true, "required": false, "position": 2}, - "custom_events": {"enabled": true, "required": false, "position": 3}, - "custom_parameters": {"enabled": false, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": true, "required": false, "position": 5}, - "message_parameters": {"enabled": true, "required": false, "position": 6}, - "job_arguments": {"enabled": true, "required": false, "position": 7} - }, - "expected_connect_policies": { - "custom_parameters": {"enabled": false} - }, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "custom_parameters": {"enabled": false} - }, - "should_log": false, - "should_shutdown": false -}, { - "name": "should respect custom_instrumentation_editor policy", - "required_features": ["custom_instrumentation_editor"], - "starting_policy_settings": { - "custom_instrumentation_editor": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": true, "required": false, "position": 0}, - "attributes_include": {"enabled": true, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": true, "required": false, "position": 2}, - "custom_events": {"enabled": true, "required": false, "position": 3}, - "custom_parameters": {"enabled": true, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": false, "required": false, "position": 5}, - "message_parameters": {"enabled": true, "required": false, "position": 6}, - "job_arguments": {"enabled": true, "required": false, "position": 7} - }, - "expected_connect_policies": { - "custom_instrumentation_editor": {"enabled": false} - }, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "custom_instrumentation_editor": {"enabled": false} - }, - "should_log": false, - "should_shutdown": false -}, { - "name": "should respect message_parameters policy", - "required_features": ["message_parameters"], - "starting_policy_settings": { - "message_parameters": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": true, "required": false, "position": 0}, - "attributes_include": {"enabled": true, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": true, "required": false, "position": 2}, - "custom_events": {"enabled": true, "required": false, "position": 3}, - "custom_parameters": {"enabled": true, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": true, "required": false, "position": 5}, - "message_parameters": {"enabled": false, "required": false, "position": 6}, - "job_arguments": {"enabled": true, "required": false, "position": 7} - }, - "expected_connect_policies": { - "message_parameters": {"enabled": false} - }, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "message_parameters": {"enabled": false} - }, - "should_log": false, - "should_shutdown": false -}, { - "name": "should respect job_arguments policy", - "required_features": ["job_arguments"], - "starting_policy_settings": { - "job_arguments": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": true, "required": false, "position": 0}, - "attributes_include": {"enabled": true, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": true, "required": false, "position": 2}, - "custom_events": {"enabled": true, "required": false, "position": 3}, - "custom_parameters": {"enabled": true, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": true, "required": false, "position": 5}, - "message_parameters": {"enabled": true, "required": false, "position": 6}, - "job_arguments": {"enabled": false, "required": false, "position": 7} - }, - "expected_connect_policies": { - "job_arguments": {"enabled": false} - }, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "job_arguments": {"enabled": false} - }, - "should_log": false, - "should_shutdown": false -}, { - "name": "should fail because the agent knows about a policy the server does not", - "required_features": ["record_sql"], - "starting_policy_settings": { - "record_sql": {"enabled": true} - }, - "security_policies": {}, - "expected_connect_policies": {}, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "record_sql": {"enabled": true} - }, - "should_log": true, - "should_shutdown": true -}, { - "name": "should not respond with unknown policies", - "required_features": ["record_sql"], - "starting_policy_settings": { - "record_sql": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": false, "required": false, "position": 0}, - "attributes_include": {"enabled": true, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": true, "required": false, "position": 2}, - "custom_events": {"enabled": true, "required": false, "position": 3}, - "custom_parameters": {"enabled": true, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": true, "required": false, "position": 5}, - "message_parameters": {"enabled": true, "required": false, "position": 6}, - "job_arguments": {"enabled": true, "required": false, "position": 7}, - "some_new_feature": {"enabled": false, "required": false, "position": 8} - }, - "expected_connect_policies": { - "record_sql": {"enabled": false} - }, - "validate_policies_not_in_connect": [ - "some_new_feature" - ], - "ending_policy_settings": { - "record_sql": {"enabled": false} - }, - "should_log": false, - "should_shutdown": false -}, { - "name": "should shutdown for required but unknown policies", - "required_features": ["record_sql"], - "starting_policy_settings": { - "record_sql": {"enabled": true} - }, - "security_policies": { - "record_sql": {"enabled": false, "required": false, "position": 0}, - "attributes_include": {"enabled": true, "required": false, "position": 1}, - "allow_raw_exception_messages": {"enabled": true, "required": false, "position": 2}, - "custom_events": {"enabled": true, "required": false, "position": 3}, - "custom_parameters": {"enabled": true, "required": false, "position": 4}, - "custom_instrumentation_editor": {"enabled": true, "required": false, "position": 5}, - "message_parameters": {"enabled": true, "required": false, "position": 6}, - "job_arguments": {"enabled": true, "required": false, "position": 7}, - "some_new_feature": {"enabled": false, "required": true, "position": 8} - }, - "expected_connect_policies": { - "record_sql": {"enabled": false} - }, - "validate_policies_not_in_connect": [], - "ending_policy_settings": { - "record_sql": {"enabled": false} - }, - "should_log": true, - "should_shutdown": true -}] diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/README.md b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/README.md deleted file mode 100644 index f839f9e6a..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# PostgreSQL explain plan obfuscation tests - -These tests show how explain plans for PostgreSQL should be obfuscated when -SQL obfuscation is enabled. Obfuscation of explain plans for PostgreSQL is -necessary because they can include portions of the original query that may -contain sensitive data. - -Each test case consists of a set of files with the following extensions: - -* `.query.txt` - the original SQL query that is being explained -* `.explain.txt` - the raw un-obfuscated output from running `EXPLAIN ` -* `.colon_obfuscated.txt` - the desired obfuscated explain output if using the -default, more aggressive obfuscation strategy described [here](https://newrelic.atlassian.net/wiki/display/eng/Obfuscating+PostgreSQL+Explain+plans). -* `.obfuscated.txt` - the desired obfuscated explain output if using a more -accurate, less aggressive obfuscation strategy detailed in this -[Jive thread](https://newrelic.jiveon.com/thread/1851). diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.colon_obfuscated.txt deleted file mode 100644 index 7ec5fb0e6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.colon_obfuscated.txt +++ /dev/null @@ -1,3 +0,0 @@ - Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540) - Index Cond: ? - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.explain.txt deleted file mode 100644 index 17756f238..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.explain.txt +++ /dev/null @@ -1,3 +0,0 @@ - Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540) - Index Cond: (id = 1234) - Filter: ((title)::text = 'sensitive text'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.obfuscated.txt deleted file mode 100644 index 0302012b5..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.obfuscated.txt +++ /dev/null @@ -1,3 +0,0 @@ - Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540) - Index Cond: (id = ?) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.query.txt deleted file mode 100644 index 98504f2b1..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/basic_where.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.id=1234 AND blogs.title='sensitive text' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.colon_obfuscated.txt deleted file mode 100644 index 39cf4175e..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_4 (cost=0.00..56.60 rows=1 width=5) - Filter: ? diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.explain.txt deleted file mode 100644 index 8005a6395..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_4 (cost=0.00..56.60 rows=1 width=5) - Filter: ((j = 'a'::"char") AND (k = ('now'::cstring)::date)) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.obfuscated.txt deleted file mode 100644 index d83600f81..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_4 (cost=0.00..56.60 rows=1 width=5) - Filter: ((j = ?::"char") AND (k = (?::cstring)::date)) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.query.txt deleted file mode 100644 index 235ec0bb1..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/current_date.query.txt +++ /dev/null @@ -1 +0,0 @@ -explain select * from explain_plan_test_4 where j = 'abcd' and k = current_date diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.colon_obfuscated.txt deleted file mode 100644 index b3cafd41b..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_4 (cost=0.00..39.12 rows=12 width=5) - Filter: ? diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.explain.txt deleted file mode 100644 index dac8471e3..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_4 (cost=0.00..39.12 rows=12 width=5) - Filter: (k = '2001-09-28'::date) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.obfuscated.txt deleted file mode 100644 index bbf32e474..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_4 (cost=0.00..39.12 rows=12 width=5) - Filter: (k = ?::date) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.query.txt deleted file mode 100644 index 8b13eac41..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/date.query.txt +++ /dev/null @@ -1 +0,0 @@ -explain select * from explain_plan_test_4 where k = date '2001-09-28'" diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.colon_obfuscated.txt deleted file mode 100644 index 45c36b5e3..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on blogs (cost=0.00..1.01 rows=1 width=540) - Filter: ? diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.explain.txt deleted file mode 100644 index 6f154a24e..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.explain.txt +++ /dev/null @@ -1,3 +0,0 @@ -Seq Scan on blogs (cost=0.00..1.01 rows=1 width=540) - Filter: ((title)::text = '\x08\x0C - \r '::text) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.obfuscated.txt deleted file mode 100644 index bca13233a..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on blogs (cost=0.00..1.01 rows=1 width=540) - Filter: ((title)::text = ?::text) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.query.txt deleted file mode 100644 index 230411c59..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_newline.query.txt +++ /dev/null @@ -1 +0,0 @@ -select * from blogs where title = E'\x08\x0c\n\r\t' diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.colon_obfuscated.txt deleted file mode 100644 index 4a95be3ef..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_1 (cost=0.00..24.50 rows=6 width=40) - Filter: ? diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.explain.txt deleted file mode 100644 index 8b400e7d1..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_1 (cost=0.00..24.50 rows=6 width=40) - Filter: (c = 'three''three'::text) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.obfuscated.txt deleted file mode 100644 index 3b90e9c81..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_1 (cost=0.00..24.50 rows=6 width=40) - Filter: (c = ?::text) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.query.txt deleted file mode 100644 index c3ddc49e7..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/embedded_quote.query.txt +++ /dev/null @@ -1 +0,0 @@ -explain select * from explain_plan_test_1 where c = 'three''three' diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.colon_obfuscated.txt deleted file mode 100644 index c76e41e9a..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_1 (cost=0.00..27.40 rows=6 width=40) - Filter: ? diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.explain.txt deleted file mode 100644 index c9efd5484..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_1 (cost=0.00..27.40 rows=6 width=40) - Filter: ((a)::numeric = 10000000000::numeric) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.obfuscated.txt deleted file mode 100644 index 7595d016a..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on explain_plan_test_1 (cost=0.00..27.40 rows=6 width=40) - Filter: ((a)::numeric = ?::numeric) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.query.txt deleted file mode 100644 index 7f30e0b10..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/floating_point.query.txt +++ /dev/null @@ -1 +0,0 @@ -explain select * from explain_plan_test_1 where a = 1e10 diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.colon_obfuscated.txt deleted file mode 100644 index b699b0744..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.colon_obfuscated.txt +++ /dev/null @@ -1,5 +0,0 @@ - Hash Join (cost=12.93..26.33 rows=130 width=1113) - Hash Cond: ? - -> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=540) - -> Hash (cost=11.30..11.30 rows=130 width=573) - -> Seq Scan on posts (cost=0.00..11.30 rows=130 width=573) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.explain.txt deleted file mode 100644 index 7b7b2fa64..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.explain.txt +++ /dev/null @@ -1,5 +0,0 @@ - Hash Join (cost=12.93..26.33 rows=130 width=1113) - Hash Cond: (pg_catalog.concat(blogs.title, '-suffix') = (posts.title)::text) - -> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=540) - -> Hash (cost=11.30..11.30 rows=130 width=573) - -> Seq Scan on posts (cost=0.00..11.30 rows=130 width=573) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.obfuscated.txt deleted file mode 100644 index ac0e0ee83..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.obfuscated.txt +++ /dev/null @@ -1,5 +0,0 @@ - Hash Join (cost=12.93..26.33 rows=130 width=1113) - Hash Cond: (pg_catalog.concat(blogs.title, ?) = (posts.title)::text) - -> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=540) - -> Hash (cost=11.30..11.30 rows=130 width=573) - -> Seq Scan on posts (cost=0.00..11.30 rows=130 width=573) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.query.txt deleted file mode 100644 index be642a22d..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/function_with_strings.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs JOIN posts ON posts.title=CONCAT(blogs.title, '-suffix') \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.colon_obfuscated.txt deleted file mode 100644 index 7e5d01c89..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on "explain_plan_test'_3" (cost=0.00..24.50 rows=6 width=40) - Filter: ? diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.explain.txt deleted file mode 100644 index ffd06faaf..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on "explain_plan_test'_3" (cost=0.00..24.50 rows=6 width=40) - Filter: (i = '"abcd"'::text) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.obfuscated.txt deleted file mode 100644 index cdfec9a89..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ -Seq Scan on "explain_plan_test'_3" (cost=0.00..24.50 rows=6 width=40) - Filter: (i = ?::text) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.query.txt deleted file mode 100644 index 01bfc59d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/quote_in_table_name.query.txt +++ /dev/null @@ -1 +0,0 @@ -explain select * from "explain_plan_test'_3" where i = '"abcd"' diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.colon_obfuscated.txt deleted file mode 100644 index e2ad7da4d..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.colon_obfuscated.txt +++ /dev/null @@ -1,5 +0,0 @@ -Insert on explain_plan_test_1 (cost=24.50..49.00 rows=580 width=40) - -> Seq Scan on explain_plan_test_2 (cost=24.50..49.00 rows=580 width=40) - Filter: ? - SubPlan 1 - -> Seq Scan on explain_plan_test_1 (cost=0.00..21.60 rows=1160 width=4) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.explain.txt deleted file mode 100644 index 3f6d93dbc..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.explain.txt +++ /dev/null @@ -1,5 +0,0 @@ -Insert on explain_plan_test_1 (cost=24.50..49.00 rows=580 width=40) - -> Seq Scan on explain_plan_test_2 (cost=24.50..49.00 rows=580 width=40) - Filter: (NOT (hashed SubPlan 1)) - SubPlan 1 - -> Seq Scan on explain_plan_test_1 (cost=0.00..21.60 rows=1160 width=4) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.obfuscated.txt deleted file mode 100644 index 3f6d93dbc..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.obfuscated.txt +++ /dev/null @@ -1,5 +0,0 @@ -Insert on explain_plan_test_1 (cost=24.50..49.00 rows=580 width=40) - -> Seq Scan on explain_plan_test_2 (cost=24.50..49.00 rows=580 width=40) - Filter: (NOT (hashed SubPlan 1)) - SubPlan 1 - -> Seq Scan on explain_plan_test_1 (cost=0.00..21.60 rows=1160 width=4) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.query.txt deleted file mode 100644 index 4c63710b0..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/subplan.query.txt +++ /dev/null @@ -1 +0,0 @@ -explain insert into explain_plan_test_1 select * from explain_plan_test_2 where explain_plan_test_2.d not in (select a from explain_plan_test_1) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.colon_obfuscated.txt deleted file mode 100644 index 28a296df9..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540) - Index Cond: ? diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.explain.txt deleted file mode 100644 index 904186fa3..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540) - Index Cond: (id = 1234) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.obfuscated.txt deleted file mode 100644 index bc1714cd2..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540) - Index Cond: (id = ?) diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.query.txt deleted file mode 100644 index bf9f428c4..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_integer.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.id=1234 diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.explain.txt deleted file mode 100644 index 7701fbb37..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = '][^|)/('::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.query.txt deleted file mode 100644 index d3216f3e5..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_regex_chars.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.title='][^|)/(' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.colon_obfuscated.txt deleted file mode 100644 index 7ec5fb0e6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.colon_obfuscated.txt +++ /dev/null @@ -1,3 +0,0 @@ - Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540) - Index Cond: ? - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.explain.txt deleted file mode 100644 index c6a46a121..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.explain.txt +++ /dev/null @@ -1,3 +0,0 @@ - Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540) - Index Cond: (id = 15402) - Filter: ((title)::text = 'logs'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.obfuscated.txt deleted file mode 100644 index 0302012b5..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.obfuscated.txt +++ /dev/null @@ -1,3 +0,0 @@ - Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540) - Index Cond: (id = ?) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.query.txt deleted file mode 100644 index 32dca5b5b..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/where_with_substring.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.id=15402 AND blogs.title='logs' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.explain.txt deleted file mode 100644 index 79e1da81a..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = 'foo''bar'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.query.txt deleted file mode 100644 index 2854fb92b..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case1.query.txt +++ /dev/null @@ -1 +0,0 @@ -EXPLAIN SELECT * FROM blogs WHERE blogs.title=E'foo\'bar' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.explain.txt deleted file mode 100644 index 956a40e8b..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.explain.txt +++ /dev/null @@ -1,3 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = '\x08\x0C - \r '::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.query.txt deleted file mode 100644 index 3dfab9ea7..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case2.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.title=E'\b\f\n\r\t' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.explain.txt deleted file mode 100644 index 896ebec6d..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = '\x01\x079'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.query.txt deleted file mode 100644 index 05ca5b98d..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case3.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.title=E'\1\7\9' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.explain.txt deleted file mode 100644 index 79e1da81a..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = 'foo''bar'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.query.txt deleted file mode 100644 index 2247258da..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case4.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.title='foo\'bar' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.explain.txt deleted file mode 100644 index b6b473025..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = 'U'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.query.txt deleted file mode 100644 index 4089e9be7..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case5.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.title=E'\x55' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.explain.txt deleted file mode 100644 index 37f3d9e7c..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = 'data'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.query.txt deleted file mode 100644 index f8ef48f2a..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case6.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.title=E'd\u0061t\U00000061' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.explain.txt deleted file mode 100644 index 37f3d9e7c..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = 'data'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.query.txt deleted file mode 100644 index ca20c583d..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case7.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.title=U&'d\0061t\+000061' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.explain.txt deleted file mode 100644 index 3941c5a10..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = 'слон'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.query.txt deleted file mode 100644 index 08c76cb94..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case8.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.title=U&'\0441\043B\043E\043D' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.colon_obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.colon_obfuscated.txt deleted file mode 100644 index c1159e0d6..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.colon_obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ? \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.explain.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.explain.txt deleted file mode 100644 index 37f3d9e7c..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.explain.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = 'data'::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.obfuscated.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.obfuscated.txt deleted file mode 100644 index 083affa27..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.obfuscated.txt +++ /dev/null @@ -1,2 +0,0 @@ - Seq Scan on blogs (cost=0.00..11.75 rows=1 width=540) - Filter: ((title)::text = ?::text) \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.query.txt b/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.query.txt deleted file mode 100644 index 69184644f..000000000 --- a/internal/crossagent/cross_agent_tests/postgres_explain_obfuscation/with_escape_case9.query.txt +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM blogs WHERE blogs.title=U&'d!0061t!+000061' UESCAPE '!' \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_1core_1logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_1core_1logical.txt deleted file mode 100644 index 7476bebad..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_1core_1logical.txt +++ /dev/null @@ -1,3 +0,0 @@ -processor : 0 -model name : AMD Duron(tm) processor -cache size : 64 KB diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_1core_2logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_1core_2logical.txt deleted file mode 100644 index fab5fa56a..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_1core_2logical.txt +++ /dev/null @@ -1,14 +0,0 @@ -processor : 0 -model name : Intel(R) Pentium(R) 4 CPU 2.80GHz -cache size : 1024 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 1 -processor : 1 -model name : Intel(R) Pentium(R) 4 CPU 2.80GHz -cache size : 1024 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 1 diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_2core_2logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_2core_2logical.txt deleted file mode 100644 index 0281fcf51..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_2core_2logical.txt +++ /dev/null @@ -1,14 +0,0 @@ -processor : 0 -model name : Intel(R) Pentium(R) D CPU 3.00GHz -cache size : 2048 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 2 -processor : 1 -model name : Intel(R) Pentium(R) D CPU 3.00GHz -cache size : 2048 KB -physical id : 0 -siblings : 2 -core id : 1 -cpu cores : 2 diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_4core_4logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_4core_4logical.txt deleted file mode 100644 index 0a3bf3781..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/1pack_4core_4logical.txt +++ /dev/null @@ -1,28 +0,0 @@ -processor : 0 -model name : Intel(R) Xeon(R) CPU E5410 @ 2.33GHz -cache size : 6144 KB -physical id : 0 -siblings : 4 -core id : 0 -cpu cores : 4 -processor : 1 -model name : Intel(R) Xeon(R) CPU E5410 @ 2.33GHz -cache size : 6144 KB -physical id : 0 -siblings : 4 -core id : 1 -cpu cores : 4 -processor : 2 -model name : Intel(R) Xeon(R) CPU E5410 @ 2.33GHz -cache size : 6144 KB -physical id : 0 -siblings : 4 -core id : 2 -cpu cores : 4 -processor : 3 -model name : Intel(R) Xeon(R) CPU E5410 @ 2.33GHz -cache size : 6144 KB -physical id : 0 -siblings : 4 -core id : 3 -cpu cores : 4 diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_12core_24logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_12core_24logical.txt deleted file mode 100644 index 4177c0361..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_12core_24logical.txt +++ /dev/null @@ -1,575 +0,0 @@ -processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 0 -cpu cores : 6 -apicid : 32 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.18 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 1 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 0 -cpu cores : 6 -apicid : 0 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.05 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 2 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 1 -cpu cores : 6 -apicid : 34 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.02 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 3 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 1 -cpu cores : 6 -apicid : 2 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.05 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 4 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 2 -cpu cores : 6 -apicid : 36 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5319.95 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 5 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 2 -cpu cores : 6 -apicid : 4 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.06 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 6 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 8 -cpu cores : 6 -apicid : 48 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.02 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 7 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 8 -cpu cores : 6 -apicid : 16 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5319.98 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 8 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 9 -cpu cores : 6 -apicid : 50 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.02 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 9 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 9 -cpu cores : 6 -apicid : 18 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5319.99 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 10 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 10 -cpu cores : 6 -apicid : 52 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.02 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 11 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 10 -cpu cores : 6 -apicid : 20 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.02 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 12 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 0 -cpu cores : 6 -apicid : 33 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5319.94 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 13 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 0 -cpu cores : 6 -apicid : 1 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.05 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 14 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 1 -cpu cores : 6 -apicid : 35 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.02 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 15 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 1 -cpu cores : 6 -apicid : 3 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.05 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 16 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 2 -cpu cores : 6 -apicid : 37 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.02 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 17 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 2 -cpu cores : 6 -apicid : 5 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.05 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 18 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 8 -cpu cores : 6 -apicid : 49 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5330.86 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 19 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 8 -cpu cores : 6 -apicid : 17 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.05 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 20 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 9 -cpu cores : 6 -apicid : 51 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.02 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 21 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 9 -cpu cores : 6 -apicid : 19 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.01 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 22 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 1 -siblings : 12 -core id : 10 -cpu cores : 6 -apicid : 53 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.05 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] - -processor : 23 -vendor_id : GenuineIntel -cpu family : 6 -model : 44 -model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz -stepping : 2 -cpu MHz : 2660.090 -cache size : 12288 KB -physical id : 0 -siblings : 12 -core id : 10 -cpu cores : 6 -apicid : 21 -fpu : yes -fpu_exception : yes -cpuid level : 11 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx pdpe1gb rdtscp lm constant_tsc ida nonstop_tsc arat pni monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lahf_lm -bogomips : 5320.02 -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: [8] diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_20core_40logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_20core_40logical.txt deleted file mode 100644 index 709087d31..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_20core_40logical.txt +++ /dev/null @@ -1,999 +0,0 @@ -processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 0 -cpu cores : 10 -apicid : 0 -initial apicid : 0 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 1 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 1 -cpu cores : 10 -apicid : 2 -initial apicid : 2 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 2 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 2 -cpu cores : 10 -apicid : 4 -initial apicid : 4 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 3 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 3 -cpu cores : 10 -apicid : 6 -initial apicid : 6 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 4 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 4 -cpu cores : 10 -apicid : 8 -initial apicid : 8 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 5 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 8 -cpu cores : 10 -apicid : 16 -initial apicid : 16 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 6 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 9 -cpu cores : 10 -apicid : 18 -initial apicid : 18 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 7 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 10 -cpu cores : 10 -apicid : 20 -initial apicid : 20 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 8 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 11 -cpu cores : 10 -apicid : 22 -initial apicid : 22 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 9 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 12 -cpu cores : 10 -apicid : 24 -initial apicid : 24 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 10 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 0 -cpu cores : 10 -apicid : 32 -initial apicid : 32 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 11 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 1 -cpu cores : 10 -apicid : 34 -initial apicid : 34 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 12 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 2 -cpu cores : 10 -apicid : 36 -initial apicid : 36 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 13 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 3 -cpu cores : 10 -apicid : 38 -initial apicid : 38 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 14 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 4 -cpu cores : 10 -apicid : 40 -initial apicid : 40 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 15 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 8 -cpu cores : 10 -apicid : 48 -initial apicid : 48 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 16 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 9 -cpu cores : 10 -apicid : 50 -initial apicid : 50 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 17 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 10 -cpu cores : 10 -apicid : 52 -initial apicid : 52 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 18 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 11 -cpu cores : 10 -apicid : 54 -initial apicid : 54 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 19 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 2801.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 12 -cpu cores : 10 -apicid : 56 -initial apicid : 56 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 20 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 0 -cpu cores : 10 -apicid : 1 -initial apicid : 1 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 21 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 1 -cpu cores : 10 -apicid : 3 -initial apicid : 3 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 22 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 2 -cpu cores : 10 -apicid : 5 -initial apicid : 5 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 23 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 3 -cpu cores : 10 -apicid : 7 -initial apicid : 7 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 24 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 4 -cpu cores : 10 -apicid : 9 -initial apicid : 9 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 25 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 8 -cpu cores : 10 -apicid : 17 -initial apicid : 17 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 26 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 9 -cpu cores : 10 -apicid : 19 -initial apicid : 19 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 27 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 10 -cpu cores : 10 -apicid : 21 -initial apicid : 21 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 28 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 11 -cpu cores : 10 -apicid : 23 -initial apicid : 23 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 29 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 0 -siblings : 20 -core id : 12 -cpu cores : 10 -apicid : 25 -initial apicid : 25 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5586.71 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 30 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 0 -cpu cores : 10 -apicid : 33 -initial apicid : 33 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 31 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 1 -cpu cores : 10 -apicid : 35 -initial apicid : 35 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 32 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 2 -cpu cores : 10 -apicid : 37 -initial apicid : 37 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 33 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 3 -cpu cores : 10 -apicid : 39 -initial apicid : 39 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 34 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 4 -cpu cores : 10 -apicid : 41 -initial apicid : 41 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 35 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 8 -cpu cores : 10 -apicid : 49 -initial apicid : 49 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 36 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 9 -cpu cores : 10 -apicid : 51 -initial apicid : 51 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 37 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 10 -cpu cores : 10 -apicid : 53 -initial apicid : 53 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 38 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 11 -cpu cores : 10 -apicid : 55 -initial apicid : 55 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 39 -vendor_id : GenuineIntel -cpu family : 6 -model : 62 -model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz -stepping : 4 -cpu MHz : 1200.000 -cache size : 25600 KB -physical id : 1 -siblings : 20 -core id : 12 -cpu cores : 10 -apicid : 57 -initial apicid : 57 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms -bogomips : 5585.83 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_2core_2logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_2core_2logical.txt deleted file mode 100644 index f75e9fac9..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_2core_2logical.txt +++ /dev/null @@ -1,51 +0,0 @@ -processor : 0 -vendor_id : AuthenticAMD -cpu family : 15 -model : 33 -model name : Dual Core AMD Opteron(tm) Processor 270 -stepping : 2 -cpu MHz : 2004.546 -cache size : 1024 KB -physical id : 0 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 1 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fx -sr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow pni lahf_lm cmp_lega -cy -bogomips : 4011.21 -TLB size : 1024 4K pages -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: ts fid vid ttp - -processor : 1 -vendor_id : AuthenticAMD -cpu family : 15 -model : 33 -model name : Dual Core AMD Opteron(tm) Processor 270 -stepping : 2 -cpu MHz : 2004.546 -cache size : 1024 KB -physical id : 1 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 1 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fx -sr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow up pni lahf_lm cmp_l -egacy -bogomips : 4011.21 -TLB size : 1024 4K pages -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: ts fid vid ttp diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_2core_4logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_2core_4logical.txt deleted file mode 100644 index cedcb7aa4..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_2core_4logical.txt +++ /dev/null @@ -1,28 +0,0 @@ -processor : 0 -model name : Intel(R) Xeon(TM) CPU 3.60GHz -cache size : 1024 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 1 -processor : 1 -model name : Intel(R) Xeon(TM) CPU 3.60GHz -cache size : 1024 KB -physical id : 3 -siblings : 2 -core id : 0 -cpu cores : 1 -processor : 2 -model name : Intel(R) Xeon(TM) CPU 3.60GHz -cache size : 1024 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 1 -processor : 3 -model name : Intel(R) Xeon(TM) CPU 3.60GHz -cache size : 1024 KB -physical id : 3 -siblings : 2 -core id : 0 -cpu cores : 1 diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_4core_4logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_4core_4logical.txt deleted file mode 100644 index 6e6f418b9..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/2pack_4core_4logical.txt +++ /dev/null @@ -1,28 +0,0 @@ -processor : 0 -model name : Intel(R) Xeon(R) CPU 5160 @ 3.00GHz -cache size : 4096 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 2 -processor : 1 -model name : Intel(R) Xeon(R) CPU 5160 @ 3.00GHz -cache size : 4096 KB -physical id : 0 -siblings : 2 -core id : 1 -cpu cores : 2 -processor : 2 -model name : Intel(R) Xeon(R) CPU 5160 @ 3.00GHz -cache size : 4096 KB -physical id : 3 -siblings : 2 -core id : 0 -cpu cores : 2 -processor : 3 -model name : Intel(R) Xeon(R) CPU 5160 @ 3.00GHz -cache size : 4096 KB -physical id : 3 -siblings : 2 -core id : 1 -cpu cores : 2 diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/4pack_4core_4logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/4pack_4core_4logical.txt deleted file mode 100644 index c7a816e12..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/4pack_4core_4logical.txt +++ /dev/null @@ -1,103 +0,0 @@ -processor : 0 -vendor_id : AuthenticAMD -cpu family : 15 -model : 65 -model name : Dual-Core AMD Opteron(tm) Processor 2218 HE -stepping : 3 -cpu MHz : 2599.998 -cache size : 1024 KB -physical id : 0 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 1 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fx -sr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow pni cx16 lahf -_lm cmp_legacy svm extapic cr8_legacy -bogomips : 5202.15 -TLB size : 1024 4K pages -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: ts fid vid ttp tm stc - -processor : 1 -vendor_id : AuthenticAMD -cpu family : 15 -model : 65 -model name : Dual-Core AMD Opteron(tm) Processor 2218 HE -stepping : 3 -cpu MHz : 2599.998 -cache size : 1024 KB -physical id : 1 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 1 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fx -sr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow up pni cx16 l -ahf_lm cmp_legacy svm extapic cr8_legacy -bogomips : 5202.15 -TLB size : 1024 4K pages -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: ts fid vid ttp tm stc - -processor : 2 -vendor_id : AuthenticAMD -cpu family : 15 -model : 65 -model name : Dual-Core AMD Opteron(tm) Processor 2218 HE -stepping : 3 -cpu MHz : 2599.998 -cache size : 1024 KB -physical id : 2 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 1 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fx -sr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow up pni cx16 l -ahf_lm cmp_legacy svm extapic cr8_legacy -bogomips : 5202.15 -TLB size : 1024 4K pages -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: ts fid vid ttp tm stc - -processor : 3 -vendor_id : AuthenticAMD -cpu family : 15 -model : 65 -model name : Dual-Core AMD Opteron(tm) Processor 2218 HE -stepping : 3 -cpu MHz : 2599.998 -cache size : 1024 KB -physical id : 3 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 1 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush mmx fx -sr sse sse2 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow up pni cx16 l -ahf_lm cmp_legacy svm extapic cr8_legacy -bogomips : 5202.15 -TLB size : 1024 4K pages -clflush size : 64 -cache_alignment : 64 -address sizes : 40 bits physical, 48 bits virtual -power management: ts fid vid ttp tm stc diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/8pack_8core_8logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/8pack_8core_8logical.txt deleted file mode 100644 index afdb880e5..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/8pack_8core_8logical.txt +++ /dev/null @@ -1,199 +0,0 @@ -processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -physical id : 0 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm syscall nx lm constant_tsc pni monitor ds_cpl vmx -est tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 4654.10 -clflush size : 64 -cache_alignment : 64 -address sizes : 38 bits physical, 48 bits virtual -power management: - -processor : 1 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -physical id : 1 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm syscall nx lm constant_tsc up pni monitor ds_cpl v -mx est tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 4654.10 -clflush size : 64 -cache_alignment : 64 -address sizes : 38 bits physical, 48 bits virtual -power management: - -processor : 2 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -physical id : 2 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm syscall nx lm constant_tsc up pni monitor ds_cpl v -mx est tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 4654.10 -clflush size : 64 -cache_alignment : 64 -address sizes : 38 bits physical, 48 bits virtual -power management: - -processor : 3 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -physical id : 3 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm syscall nx lm constant_tsc up pni monitor ds_cpl v -mx est tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 4654.10 -clflush size : 64 -cache_alignment : 64 -address sizes : 38 bits physical, 48 bits virtual -power management: - -processor : 4 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -physical id : 4 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm syscall nx lm constant_tsc up pni monitor ds_cpl v -mx est tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 4654.10 -clflush size : 64 -cache_alignment : 64 -address sizes : 38 bits physical, 48 bits virtual -power management: - -processor : 5 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -physical id : 5 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm syscall nx lm constant_tsc up pni monitor ds_cpl v -mx est tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 4654.10 -clflush size : 64 -cache_alignment : 64 -address sizes : 38 bits physical, 48 bits virtual -power management: - -processor : 6 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -physical id : 6 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm syscall nx lm constant_tsc up pni monitor ds_cpl v -mx est tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 4654.10 -clflush size : 64 -cache_alignment : 64 -address sizes : 38 bits physical, 48 bits virtual -power management: - -processor : 7 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -physical id : 7 -siblings : 1 -core id : 0 -cpu cores : 1 -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm syscall nx lm constant_tsc up pni monitor ds_cpl v -mx est tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 4654.10 -clflush size : 64 -cache_alignment : 64 -address sizes : 38 bits physical, 48 bits virtual -power management: diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/README.md b/internal/crossagent/cross_agent_tests/proc_cpuinfo/README.md deleted file mode 100644 index 04997821a..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/README.md +++ /dev/null @@ -1,28 +0,0 @@ -These tests are for determining the numbers of physical packages, physical cores, -and logical processors from the data returned by /proc/cpuinfo on Linux hosts. -Each text file in this directory is the output of /proc/cpuinfo on various machines. - -The names of all test files should be of the form `Apack_Bcore_Clogical.txt` -where `A`, `B`, and `C` are integers or the character `X`. For example, -a single quad-core processor without hyperthreading would correspond to -`1pack_4core_4logical.txt`, while two 6-core processors with hyperthreading -would correspond to `2pack_12core_24logical.txt`, and would be pretty sweet. - -Using `A`, `B`, and `C` from above, code processing the text in these files -should produce the following expected values: - -| property | value | -| -------------------- |---------| -| # physical packages | `A` | -| # physical cores | `B` | -| # logical processors | `C` | - -(Obviously, the processing code should do this with no knowledge of the filenames.) - -If any of `A`, `B`, or `C` are the character `X` instead of an integer, then -processing code should not return a value (return `null`, return `nil`, -raise an exception... whatever makes most sense for your agent). - -There is a malformed.txt file which is a random file that does not adhere to -any /proc/cpuinfo format. The expected result is `null` for packages, cores and -processors. diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/Xpack_Xcore_2logical.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/Xpack_Xcore_2logical.txt deleted file mode 100644 index 9a8e9dab5..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/Xpack_Xcore_2logical.txt +++ /dev/null @@ -1,43 +0,0 @@ -processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -fdiv_bug : no -hlt_bug : no -f00f_bug : no -coma_bug : no -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc pni monitor ds_cpl vmx est -tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 5821.98 -clflush size : 64 - -processor : 1 -vendor_id : GenuineIntel -cpu family : 6 -model : 15 -model name : Intel(R) Xeon(R) CPU E5345 @ 2.33GHz -stepping : 11 -cpu MHz : 2327.498 -cache size : 4096 KB -fdiv_bug : no -hlt_bug : no -f00f_bug : no -coma_bug : no -fpu : yes -fpu_exception : yes -cpuid level : 10 -wp : yes -flags : fpu tsc msr pae mce cx8 apic mca cmov pat pse36 clflush dts ac -pi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc up pni monitor ds_cpl vmx e -st tm2 ssse3 cx16 xtpr dca lahf_lm -bogomips : 5821.98 -clflush size : 64 diff --git a/internal/crossagent/cross_agent_tests/proc_cpuinfo/malformed_file.txt b/internal/crossagent/cross_agent_tests/proc_cpuinfo/malformed_file.txt deleted file mode 100644 index 5dfa01375..000000000 --- a/internal/crossagent/cross_agent_tests/proc_cpuinfo/malformed_file.txt +++ /dev/null @@ -1,3 +0,0 @@ -This is a random text file that does NOT adhere to the /proc/cpuinfo format. -xxxYYYZZz - diff --git a/internal/crossagent/cross_agent_tests/proc_meminfo/README.md b/internal/crossagent/cross_agent_tests/proc_meminfo/README.md deleted file mode 100644 index 29e3f6835..000000000 --- a/internal/crossagent/cross_agent_tests/proc_meminfo/README.md +++ /dev/null @@ -1,7 +0,0 @@ -These tests are for determining the physical memory from the data returned by -/proc/meminfo on Linux hosts. The total physical memory of the linux system is -reported as part of the environment values. The key used by the Python agent -is 'Total Physical Memory (MB)'. - -The names of all test files should be of the form `meminfo_nnnnMB.txt`. The -value `nnnn` in the filename is the physical memory of that system in MB. diff --git a/internal/crossagent/cross_agent_tests/proc_meminfo/meminfo_4096MB.txt b/internal/crossagent/cross_agent_tests/proc_meminfo/meminfo_4096MB.txt deleted file mode 100644 index f9f10b250..000000000 --- a/internal/crossagent/cross_agent_tests/proc_meminfo/meminfo_4096MB.txt +++ /dev/null @@ -1,47 +0,0 @@ -MemTotal: 4194304 kB -MemFree: 931724 kB -Buffers: 146992 kB -Cached: 545044 kB -SwapCached: 0 kB -Active: 551644 kB -Inactive: 454660 kB -Active(anon): 315628 kB -Inactive(anon): 9084 kB -Active(file): 236016 kB -Inactive(file): 445576 kB -Unevictable: 0 kB -Mlocked: 0 kB -HighTotal: 1183624 kB -HighFree: 295288 kB -LowTotal: 877428 kB -LowFree: 636436 kB -SwapTotal: 1046524 kB -SwapFree: 1046524 kB -Dirty: 72 kB -Writeback: 0 kB -AnonPages: 314416 kB -Mapped: 127944 kB -Shmem: 10448 kB -Slab: 75852 kB -SReclaimable: 59144 kB -SUnreclaim: 16708 kB -KernelStack: 2984 kB -PageTables: 7552 kB -NFS_Unstable: 0 kB -Bounce: 0 kB -WritebackTmp: 0 kB -CommitLimit: 2077048 kB -Committed_AS: 2433452 kB -VmallocTotal: 122880 kB -VmallocUsed: 23288 kB -VmallocChunk: 98348 kB -HardwareCorrupted: 0 kB -AnonHugePages: 0 kB -HugePages_Total: 0 -HugePages_Free: 0 -HugePages_Rsvd: 0 -HugePages_Surp: 0 -Hugepagesize: 2048 kB -DirectMap4k: 12280 kB -DirectMap2M: 901120 kB - diff --git a/internal/crossagent/cross_agent_tests/rules.json b/internal/crossagent/cross_agent_tests/rules.json deleted file mode 100644 index 588201628..000000000 --- a/internal/crossagent/cross_agent_tests/rules.json +++ /dev/null @@ -1,162 +0,0 @@ -[ - { - "testname":"replace first", - "rules":[{"match_expression":"(psi)", "replacement":"gamma", "ignore":false, "eval_order":0}, - {"match_expression":"^/userid/.*/folderid", "replacement":"/userid/*/folderid/*", "ignore":false, "eval_order":1}, - {"match_expression":"/need_not_be_first_segment/.*", "replacement":"*/need_not_be_first_segment/*", "ignore":false, "eval_order":2}], - "tests": - [ - {"input":"/alpha/psi/beta", "expected":"/alpha/gamma/beta"}, - {"input":"/psi/beta", "expected":"/gamma/beta"}, - {"input":"/alpha/psi", "expected":"/alpha/gamma"}, - {"input":"/userid/123abc/folderid/qwerty8855", "expected":"/userid/*/folderid/*/qwerty8855"}, - {"input":"/first/need_not_be_first_segment/uiop", "expected":"/first*/need_not_be_first_segment/*"} - ] - }, - { - "testname":"resource normalization rule", - "rules":[{"match_expression":"(.*)/[^/]*.(bmp|css|gif|ico|jpg|jpeg|js|png)$", "replacement":"\\1/*.\\2", "ignore":false, "eval_order":1}], - "tests": - [ - {"input":"/test/dude/flower.jpg", "expected":"/test/dude/*.jpg"}, - {"input":"/DUDE.ICO", "expected":"/*.ICO"} - ] - }, - { - "testname":"ignore rule", - "rules":[{"match_expression":"^/artists/az/(.*)/(.*)$", "replacement":"/artists/az/*/\\2", "ignore":true, "eval_order":11}], - "tests": - [ - {"input":"/artists/az/veritas/truth.jhtml", "expected":null} - ] - }, - { - "testname":"hexadecimal each segment rule", - "rules":[{"match_expression":"^[0-9a-f]*[0-9][0-9a-f]*$", "replacement":"*", "ignore":false, "eval_order":1, "each_segment":true}], - "tests": - [ - {"input":"/test/1axxx/4babe/cafe222/bad/a1b2c3d3e4f5/ABC123/x999/111", "expected":"/test/1axxx/*/*/bad/*/*/x999/*"}, - {"input":"/test/4/dude", "expected":"/test/*/dude"}, - {"input":"/test/babe4/999x", "expected":"/test/*/999x"}, - {"input":"/glass/resource/vase/images/9ae1283", "expected":"/glass/resource/vase/images/*"}, - {"input":"/test/4/dude.jsp", "expected":"/test/*/dude.jsp"}, - {"input":"/glass/resource/vase/images/add", "expected":"/glass/resource/vase/images/add"} - ] - }, - { - "testname":"url encoded segments rule", - "rules":[{"match_expression":"(.*)%(.*)", "replacement":"*", "ignore":false, "eval_order":1, "each_segment":true, "terminate_chain":false, "replace_all":false}], - "tests": - [ - {"input":"/test/%%%/bad%%/a1b2%c3%d3e4f5/x999/111%", "expected":"/test/*/*/*/x999/*"}, - {"input":"/add-resource/vmqoiearks%1B%3R", "expected":"/add-resource/*"} - ] - }, - { - "testname":"remove all ticks", - "rules":[{"match_expression":"([^']*)'+", "replacement":"\\1", "ignore":false, "eval_order":1, "each_segment":false, "replace_all":true}], - "tests": - [ - {"input":"/test/'''/b'a''d''/a1b2'c3'd3e4f5/x999/111'", "expected":"/test//bad/a1b2c3d3e4f5/x999/111"} - ] - }, - { - "testname":"number rule", - "rules":[{"match_expression":"\\d+", "replacement":"*", "ignore":false, "eval_order":1, "each_segment":false, "replace_all":true}], - "tests": - [ - {"input":"/solr/shard03/select", "expected":"/solr/shard*/select"}, - {"input":"/hey/r2d2", "expected":"/hey/r*d*"} - ] - }, - { - "testname":"custom rules", - "rules": - [ - {"match_expression":"^/([^/]*=[^/]*&?)+", "replacement":"/all_params", "ignore":false, "eval_order":0, "each_segment":false, "terminate_chain":true}, - {"match_expression":"^/.*/PARAMS/(article|legacy_article|post|product)/.*", "replacement":"/*/PARAMS/\\1/*", "ignore":false, "eval_order":14, "each_segment":false, "terminate_chain":true}, - {"match_expression":"^/test/(.*)", "replacement":"/dude", "ignore":false, "eval_order":1, "each_segment":false, "terminate_chain":true}, - {"match_expression":"^/blah/(.*)", "replacement":"/\\1", "ignore":false, "eval_order":2, "each_segment":false, "terminate_chain":true}, - {"match_expression":"/.*(dude|man)", "replacement":"/*.\\1", "ignore":false, "eval_order":3, "each_segment":false, "terminate_chain":true}, - {"match_expression":"^/(bob)", "replacement":"/\\1ert/\\1/\\1ertson", "ignore":false, "eval_order":4, "each_segment":false, "terminate_chain":true}, - {"match_expression":"/foo(.*)", "ignore":true, "eval_order":5, "each_segment":false, "terminate_chain":true}, - {"match_expression":"^/(keep)(/)(me)", "replacement":"/\\1\\2\\3", "ignore":false, "eval_order":6, "each_segment":false, "terminate_chain":true}, - {"match_expression":"^/(keep)(/)(me)", "replacement":"/you_werent_kept", "ignore":false, "eval_order":7, "each_segment":false, "terminate_chain":true} - ], - "tests": - [ - {"input":"/xs=zs&fly=*&row=swim&id=*&", "expected":"/all_params"}, - {"input":"/zip-zap/PARAMS/article/*", "expected":"/*/PARAMS/article/*"}, - {"input":"/bob", "expected":"/bobert/bob/bobertson"}, - {"input":"/test/foobar", "expected":"/dude"}, - {"input":"/bar/test", "expected":"/bar/test"}, - {"input":"/blah/test/man", "expected":"/test/man"}, - {"input":"/oh/hey.dude", "expected":"/*.dude"}, - {"input":"/oh/hey/what/up.man", "expected":"/*.man"}, - {"input":"/foo", "expected":null}, - {"input":"/foo/foobar", "expected":null}, - {"input":"/keep/me", "expected":"/keep/me"} - ] - }, - { - "testname":"chained rules", - "rules": - [ - {"match_expression":"^[0-9a-f]*[0-9][0-9a-f]*$", "replacement":"*", "ignore":false, "eval_order":1, "each_segment":true, "terminate_chain":false}, - {"match_expression":"(.*)/fritz/(.*)", "replacement":"\\1/karl/\\2", "ignore":false, "eval_order":11, "each_segment":false, "terminate_chain":true} - ], - "tests": - [ - {"input":"/test/1axxx/4babe/fritz/x999/111", "expected":"/test/1axxx/*/karl/x999/*"} - ] - }, - { - "testname":"rule ordering (two rules match, but only one is applied due to ordering)", - "rules": - [ - {"match_expression":"/test/(.*)", "replacement":"/el_duderino", "ignore":false, "eval_order":37}, - {"match_expression":"/test/(.*)", "replacement":"/dude", "ignore":false, "eval_order":1}, - {"match_expression":"/blah/(.*)", "replacement":"/$1", "ignore":false, "eval_order":2}, - {"match_expression":"/foo(.*)", "ignore":true, "eval_order":3} - ], - "tests": - [ - {"input":"/test/foobar", "expected":"/dude"} - ] - }, - { - "testname":"stable rule sorting", - "rules": - [ - {"match_expression":"/test/(.*)", "replacement":"/you_first", "ignore":false, "eval_order":0}, - {"match_expression":"/test/(.*)", "replacement":"/no_you", "ignore":false, "eval_order":0}, - {"match_expression":"/test/(.*)", "replacement":"/please_after_you", "ignore":false, "eval_order":0} - ], - "tests": - [ - {"input":"/test/polite_seattle_drivers", "expected":"/you_first"} - ] - }, - { - "testname":"custom rule chaining", - "rules": - [ - {"match_expression":"(.*)/robertson(.*)", "replacement":"\\1/LAST_NAME\\2", "ignore":false, "eval_order":0, "terminate_chain":false}, - {"match_expression":"^/robert(.*)", "replacement":"/bob\\1", "ignore":false, "eval_order":1, "terminate_chain":true}, - {"match_expression":"/LAST_NAME", "replacement":"/fail", "ignore":false, "eval_order":2, "terminate_chain":true} - ], - "tests": - [ - {"input":"/robert/robertson", "expected":"/bob/LAST_NAME"} - ] - }, - { - "testname":"saxon's test", - "rules":[{"match_expression":"^(?!account|application).*", "replacement":"*", "ignore":false, "eval_order":0, "each_segment":true}], - "tests": - [ - {"input":"/account/myacc/application/test", "expected":"/account/*/application/*"}, - {"input":"/oh/dude/account/myacc/application", "expected":"/*/*/account/*/application"} - ] - } -] diff --git a/internal/crossagent/cross_agent_tests/rum_client_config.json b/internal/crossagent/cross_agent_tests/rum_client_config.json deleted file mode 100644 index 8f6e7cbbb..000000000 --- a/internal/crossagent/cross_agent_tests/rum_client_config.json +++ /dev/null @@ -1,91 +0,0 @@ -[ - { - "testname":"all fields present", - - "apptime_milliseconds":5, - "queuetime_milliseconds":3, - "browser_monitoring.attributes.enabled":true, - "transaction_name":"WebTransaction/brink/of/glory", - "license_key":"0000111122223333444455556666777788889999", - "connect_reply": - { - "beacon":"my_beacon", - "browser_key":"my_browser_key", - "application_id":"my_application_id", - "error_beacon":"my_error_beacon", - "js_agent_file":"my_js_agent_file" - }, - "user_attributes":{"alpha":"beta"}, - "expected": - { - "beacon":"my_beacon", - "licenseKey":"my_browser_key", - "applicationID":"my_application_id", - "transactionName":"Z1VSZENQX0JTUUZbXF4fUkJYX1oeXVQdVV9fQkk=", - "queueTime":3, - "applicationTime":5, - "atts":"SxJFEgtKE1BeQlpTEQoSUlVFUBNMTw==", - "errorBeacon":"my_error_beacon", - "agent":"my_js_agent_file" - } - }, - { - "testname":"browser_monitoring.attributes.enabled disabled", - - "apptime_milliseconds":5, - "queuetime_milliseconds":3, - "browser_monitoring.attributes.enabled":false, - "transaction_name":"WebTransaction/brink/of/glory", - "license_key":"0000111122223333444455556666777788889999", - "connect_reply": - { - "beacon":"my_beacon", - "browser_key":"my_browser_key", - "application_id":"my_application_id", - "error_beacon":"my_error_beacon", - "js_agent_file":"my_js_agent_file" - }, - "user_attributes":{"alpha":"beta"}, - "expected": - { - "beacon":"my_beacon", - "licenseKey":"my_browser_key", - "applicationID":"my_application_id", - "transactionName":"Z1VSZENQX0JTUUZbXF4fUkJYX1oeXVQdVV9fQkk=", - "queueTime":3, - "applicationTime":5, - "atts":"", - "errorBeacon":"my_error_beacon", - "agent":"my_js_agent_file" - } - }, - { - "testname":"empty js_agent_file", - "apptime_milliseconds":5, - "queuetime_milliseconds":3, - "browser_monitoring.attributes.enabled":true, - "transaction_name":"WebTransaction/brink/of/glory", - "license_key":"0000111122223333444455556666777788889999", - "connect_reply": - { - "beacon":"my_beacon", - "browser_key":"my_browser_key", - "application_id":"my_application_id", - "error_beacon":"my_error_beacon", - "js_agent_file":"" - }, - "user_attributes":{"alpha":"beta"}, - "expected": - { - "beacon":"my_beacon", - "licenseKey":"my_browser_key", - "applicationID":"my_application_id", - "transactionName":"Z1VSZENQX0JTUUZbXF4fUkJYX1oeXVQdVV9fQkk=", - "queueTime":3, - "applicationTime":5, - "atts":"SxJFEgtKE1BeQlpTEQoSUlVFUBNMTw==", - "errorBeacon":"my_error_beacon", - "agent":"" - } - } -] diff --git a/internal/crossagent/cross_agent_tests/rum_footer_insertion_location/close-body-in-comment.html b/internal/crossagent/cross_agent_tests/rum_footer_insertion_location/close-body-in-comment.html deleted file mode 100644 index 3252494c2..000000000 --- a/internal/crossagent/cross_agent_tests/rum_footer_insertion_location/close-body-in-comment.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Comment contains a close body tag - - -

The quick brown fox jumps over the lazy dog.

- - EXPECTED_RUM_FOOTER_LOCATION - diff --git a/internal/crossagent/cross_agent_tests/rum_footer_insertion_location/dynamic-iframe.html b/internal/crossagent/cross_agent_tests/rum_footer_insertion_location/dynamic-iframe.html deleted file mode 100644 index 18b82ff80..000000000 --- a/internal/crossagent/cross_agent_tests/rum_footer_insertion_location/dynamic-iframe.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - Dynamic iframe Generation - - -

The quick brown fox jumps over the lazy dog.

- - - EXPECTED_RUM_FOOTER_LOCATION - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/basic.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/basic.html deleted file mode 100644 index 4afa155ee..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/basic.html +++ /dev/null @@ -1,10 +0,0 @@ - - EXPECTED_RUM_LOADER_LOCATION - im a title - - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/body_with_attributes.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/body_with_attributes.html deleted file mode 100644 index 5442cdbec..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/body_with_attributes.html +++ /dev/null @@ -1,3 +0,0 @@ -EXPECTED_RUM_LOADER_LOCATION - This isn't great HTML but it's what we've got. - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag.html deleted file mode 100644 index b050317ba..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag.html +++ /dev/null @@ -1,11 +0,0 @@ - - - im a title - - - EXPECTED_RUM_LOADER_LOCATION - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_after_x_ua_tag.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_after_x_ua_tag.html deleted file mode 100644 index 7cbc188a2..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_after_x_ua_tag.html +++ /dev/null @@ -1,11 +0,0 @@ - - - im a title - - EXPECTED_RUM_LOADER_LOCATION - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_before_x_ua_tag.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_before_x_ua_tag.html deleted file mode 100644 index a8f5fcd23..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_before_x_ua_tag.html +++ /dev/null @@ -1,11 +0,0 @@ - - - im a title - - EXPECTED_RUM_LOADER_LOCATION - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_with_spaces.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_with_spaces.html deleted file mode 100644 index 64ba08f87..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/charset_tag_with_spaces.html +++ /dev/null @@ -1,11 +0,0 @@ - - - im a title - - - EXPECTED_RUM_LOADER_LOCATION - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/comments1.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/comments1.html deleted file mode 100644 index 08c1ada77..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/comments1.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - OPT® - - - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/comments2.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/comments2.html deleted file mode 100644 index 01d895eb4..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/comments2.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - OPT® - - - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag.html deleted file mode 100644 index 3543dab41..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag.html +++ /dev/null @@ -1,11 +0,0 @@ - - - im a title - - - EXPECTED_RUM_LOADER_LOCATION - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag_after_x_ua_tag.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag_after_x_ua_tag.html deleted file mode 100644 index 1f1f91ee3..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag_after_x_ua_tag.html +++ /dev/null @@ -1,11 +0,0 @@ - - - im a title - - EXPECTED_RUM_LOADER_LOCATION - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag_before_x_ua_tag.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag_before_x_ua_tag.html deleted file mode 100644 index ccbed78e5..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/content_type_charset_tag_before_x_ua_tag.html +++ /dev/null @@ -1,11 +0,0 @@ - - - im a title - - EXPECTED_RUM_LOADER_LOCATION - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes1.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes1.html deleted file mode 100644 index 076cc62a6..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes1.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - OPT® - - - - - - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes2.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes2.html deleted file mode 100644 index 06fd00bcd..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes2.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - OPT® - - - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes_mismatch.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes_mismatch.html deleted file mode 100644 index 4c48890db..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_quotes_mismatch.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - ication" content="xxxx" /> - - - - - - OPT® - - - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_single_quotes1.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_single_quotes1.html deleted file mode 100644 index faa63f840..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_single_quotes1.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - OPT® - - - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_single_quotes_mismatch.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_single_quotes_mismatch.html deleted file mode 100644 index 7109cc8f7..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/gt_in_single_quotes_mismatch.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - .01' content='yyyy\' /> - - - - - - OPT® - - - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/head_with_attributes.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/head_with_attributes.html deleted file mode 100644 index 07f73ada2..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/head_with_attributes.html +++ /dev/null @@ -1,10 +0,0 @@ - - EXPECTED_RUM_LOADER_LOCATION - im a title - - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/incomplete_non_meta_tags.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/incomplete_non_meta_tags.html deleted file mode 100644 index 3003f0582..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/incomplete_non_meta_tags.html +++ /dev/null @@ -1,10 +0,0 @@ - - EXPECTED_RUM_LOADER_LOCATION - - - -EXPECTED_RUM_LOADER_LOCATION - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_header.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_header.html deleted file mode 100644 index ee8e6ce07..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_header.html +++ /dev/null @@ -1,7 +0,0 @@ - - - EXPECTED_RUM_LOADER_LOCATION - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_html_and_no_header.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_html_and_no_header.html deleted file mode 100644 index 8e01bd2c8..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_html_and_no_header.html +++ /dev/null @@ -1,3 +0,0 @@ -EXPECTED_RUM_LOADER_LOCATION - This isn't great HTML but it's what we've got. - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_start_header.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_start_header.html deleted file mode 100644 index 4525b759a..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/no_start_header.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - EXPECTED_RUM_LOADER_LOCATION - Cribbed from the Java agent - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/script1.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/script1.html deleted file mode 100644 index 20b2f6b66..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/script1.html +++ /dev/null @@ -1,19 +0,0 @@ - - - EXPECTED_RUM_LOADER_LOCATION - - Castor - - - - - - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/script2.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/script2.html deleted file mode 100644 index 90b01c5e5..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/script2.html +++ /dev/null @@ -1,17 +0,0 @@ - - - EXPECTED_RUM_LOADER_LOCATION - Castor - - - - - - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag.html deleted file mode 100644 index 950d9a73e..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag.html +++ /dev/null @@ -1,10 +0,0 @@ - - - im a title - EXPECTED_RUM_LOADER_LOCATION - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_multiline.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_multiline.html deleted file mode 100644 index a801f6ca9..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_multiline.html +++ /dev/null @@ -1,11 +0,0 @@ - - - im a title - EXPECTED_RUM_LOADER_LOCATION - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_multiple_tags.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_multiple_tags.html deleted file mode 100644 index d43fec3d2..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_multiple_tags.html +++ /dev/null @@ -1,12 +0,0 @@ - - - im a title - EXPECTED_RUM_LOADER_LOCATION - - - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_spaces_around_equals.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_spaces_around_equals.html deleted file mode 100644 index dd006e746..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_spaces_around_equals.html +++ /dev/null @@ -1,10 +0,0 @@ - - - im a title - EXPECTED_RUM_LOADER_LOCATION - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_with_others.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_with_others.html deleted file mode 100644 index 57674c5f8..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_with_others.html +++ /dev/null @@ -1,11 +0,0 @@ - - - im a title - EXPECTED_RUM_LOADER_LOCATION - - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_with_spaces.html b/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_with_spaces.html deleted file mode 100644 index 215ee13b5..000000000 --- a/internal/crossagent/cross_agent_tests/rum_loader_insertion_location/x_ua_meta_tag_with_spaces.html +++ /dev/null @@ -1,10 +0,0 @@ - - - im a title - EXPECTED_RUM_LOADER_LOCATION - - - im some body text - diff --git a/internal/crossagent/cross_agent_tests/sql_obfuscation/README.md b/internal/crossagent/cross_agent_tests/sql_obfuscation/README.md deleted file mode 100644 index ab51e95ce..000000000 --- a/internal/crossagent/cross_agent_tests/sql_obfuscation/README.md +++ /dev/null @@ -1,36 +0,0 @@ -These test cases cover obfuscation (more properly, masking) of literal values -from SQL statements captured by agents. SQL statements may be captured and -attached to transaction trace nodes, or to slow SQL traces. - -`sql_obfuscation.json` contains an array of test cases. The inputs for each -test case are in the `sql` property of each object. Each test case also has an -`obfuscated` property which is an array containing at least one valid output. - -Test cases also have a `dialects` property, which is an array of strings which -specify which sql dialects the test should apply to. See "SQL Syntax Documentation" list below. This is relevant because for example, PostgreSQL uses -different identifier and string quoting rules than MySQL (most notably, -double-quoted string literals are not allowed in PostgreSQL, where -double-quotes are instead used around identifiers). - -Test cases may also contain the following properties: - * `malformed`: (boolean) test SQL queries which are not valid SQL in any - quoting mode. Some agents may choose to attempt to obfuscate these cases, - and others may instead just replace the query entirely with a placeholder - message. In some agents (such as .Net), invalid SQL is caught by the driver - which throws an exception - before the obfuscation method is called. In those cases, implementation of the obfuscation test may be unnecessary. - * `pathological`: (boolean) tests which are designed specifically to break - specific methods of obfuscation, or contain patterns that are known to be - difficult to handle correctly - * `comments`: an array of strings that could be usefult for understanding - the test. - -The following database documentation may be helpful in understanding these test -cases: -* [MySQL String Literals](http://dev.mysql.com/doc/refman/5.5/en/string-literals.html) -* [PostgreSQL String Constants](http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS) - -SQL Syntax Documentation: -* [MySQL](http://dev.mysql.com/doc/refman/5.5/en/language-structure.html) -* [PostgreSQL](http://www.postgresql.org/docs/8.4/static/sql-syntax.html) -* [Cassandra](http://docs.datastax.com/en/cql/3.1/cql/cql_reference/cql_lexicon_c.html) -* [Oracle](http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/langelems.htm) -* [SQLite](https://www.sqlite.org/lang.html) diff --git a/internal/crossagent/cross_agent_tests/sql_obfuscation/sql_obfuscation.json b/internal/crossagent/cross_agent_tests/sql_obfuscation/sql_obfuscation.json deleted file mode 100644 index 69dd2b8c4..000000000 --- a/internal/crossagent/cross_agent_tests/sql_obfuscation/sql_obfuscation.json +++ /dev/null @@ -1,650 +0,0 @@ -[ - { - "name": "back_quoted_identifiers.mysql", - "obfuscated": [ - "SELECT `t001`.`c2` FROM `t001` WHERE `t001`.`c2` = ? AND c3=? LIMIT ?" - ], - "dialects": [ - "mysql" - ], - "sql": "SELECT `t001`.`c2` FROM `t001` WHERE `t001`.`c2` = 'value' AND c3=\"othervalue\" LIMIT ?" - }, - { - "name": "comment_delimiters_in_double_quoted_strings", - "obfuscated": [ - "SELECT * FROM t WHERE foo=? AND baz=?" - ], - "dialects": [ - "mssql", - "mysql" - ], - "sql": "SELECT * FROM t WHERE foo=\"bar/*\" AND baz=\"whatever */qux\"" - }, - { - "name": "comment_delimiters_in_single_quoted_strings", - "obfuscated": [ - "SELECT * FROM t WHERE foo=? AND baz=?" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT * FROM t WHERE foo='bar/*' AND baz='whatever */qux'" - }, - { - "name": "double_quoted_identifiers.postgres", - "obfuscated": [ - "SELECT \"t001\".\"c2\" FROM \"t001\" WHERE \"t001\".\"c2\" = ? AND c3=? LIMIT ?" - ], - "dialects": [ - "postgres", - "oracle" - ], - "sql": "SELECT \"t001\".\"c2\" FROM \"t001\" WHERE \"t001\".\"c2\" = 'value' AND c3=1234 LIMIT 1" - }, - { - "name": "end_of_line_comment_in_double_quoted_string", - "obfuscated": [ - "SELECT * FROM t WHERE foo=? AND\n baz=?" - ], - "dialects": [ - "mssql", - "mysql" - ], - "sql": "SELECT * FROM t WHERE foo=\"bar--\" AND\n baz=\"qux--\"" - }, - { - "name": "end_of_line_comment_in_single_quoted_string", - "obfuscated": [ - "SELECT * FROM t WHERE foo=? AND\n baz=?" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT * FROM t WHERE foo='bar--' AND\n baz='qux--'" - }, - { - "name": "end_of_query_comment_cstyle", - "obfuscated": [ - "SELECT * FROM foo WHERE bar=? ?", - "SELECT * FROM foo WHERE bar=? " - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT * FROM foo WHERE bar='baz' /* Hide Me */" - }, - { - "name": "end_of_query_comment_doubledash", - "obfuscated": [ - "SELECT * FROM foobar WHERE password=?\n?", - "SELECT * FROM foobar WHERE password=?\n" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT * FROM foobar WHERE password='hunter2'\n-- No peeking!" - }, - { - "name": "end_of_query_comment_hash", - "obfuscated": [ - "SELECT foo, bar FROM baz WHERE password=? ?", - "SELECT foo, bar FROM baz WHERE password=? " - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT foo, bar FROM baz WHERE password='hunter2' # Secret" - }, - { - "name": "escape_string_constants.postgres", - "sql": "SELECT \"col1\", \"col2\" from \"table\" WHERE \"col3\"=E'foo\\'bar\\\\baz' AND country=e'foo\\'bar\\\\baz'", - "obfuscated": [ - "SELECT \"col1\", \"col2\" from \"table\" WHERE \"col3\"=E?", - "SELECT \"col1\", \"col2\" from \"table\" WHERE \"col3\"=E? AND country=e?" - ], - "dialects": [ - "postgres" - ], - "comments": [ - "PostgreSQL supports an alternate string quoting mode where backslash escape", - "sequences are interpreted.", - "See: http://www.postgresql.org/docs/9.3/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE" - ] - }, - { - "name": "multiple_literal_types.mysql", - "obfuscated": [ - "INSERT INTO `X` values(?,?, ? , ?, ?)" - ], - "dialects": [ - "mysql" - ], - "sql": "INSERT INTO `X` values(\"test\",0, 1 , 2, 'test')" - }, - { - "name": "numbers_in_identifiers", - "obfuscated": [ - "SELECT c11.col1, c22.col2 FROM table c11, table c22 WHERE value=?" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT c11.col1, c22.col2 FROM table c11, table c22 WHERE value='nothing'" - }, - { - "name": "numeric_literals", - "sql": "INSERT INTO X VALUES(1, 23456, 123.456, 99+100)", - "obfuscated": [ - "INSERT INTO X VALUES(?, ?, ?, ?+?)", - "INSERT INTO X VALUES(?, ?, ?.?, ?+?)" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ] - }, - { - "name": "string_double_quoted.mysql", - "obfuscated": [ - "SELECT * FROM table WHERE name=? AND value=?" - ], - "dialects": [ - "mysql" - ], - "sql": "SELECT * FROM table WHERE name=\"foo\" AND value=\"don't\"" - }, - { - "name": "string_single_quoted", - "obfuscated": [ - "SELECT * FROM table WHERE name=? AND value = ?" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT * FROM table WHERE name='foo' AND value = 'bar'" - }, - { - "name": "string_with_backslash_and_twin_single_quotes", - "obfuscated": [ - "SELECT * FROM table WHERE col=?" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT * FROM table WHERE col='foo\\''bar'", - "comments": [ - "If backslashes are being ignored in single-quoted strings", - "(standard_conforming_strings=on in PostgreSQL, or NO_BACKSLASH_ESCAPES is on", - "in MySQL), then this is valid SQL." - ] - }, - { - "name": "string_with_embedded_double_quote", - "obfuscated": [ - "SELECT * FROM table WHERE col1=? AND col2=?" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT * FROM table WHERE col1='foo\"bar' AND col2='what\"ever'" - }, - { - "name": "string_with_embedded_newline", - "obfuscated": [ - "select * from accounts where accounts.name != ? order by accounts.name" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "select * from accounts where accounts.name != 'dude \n newline' order by accounts.name" - }, - { - "name": "string_with_embedded_single_quote.mysql", - "obfuscated": [ - "SELECT * FROM table WHERE col1=? AND col2=?" - ], - "dialects": [ - "mysql" - ], - "sql": "SELECT * FROM table WHERE col1=\"don't\" AND col2=\"won't\"" - }, - { - "name": "string_with_escaped_quotes.mysql", - "sql": "INSERT INTO X values('', 'jim''s ssn',0, 1 , 'jim''s son''s son', \"\"\"jim''s\"\" hat\", \"\\\"jim''s secret\\\"\")", - "obfuscated": [ - "INSERT INTO X values(?, ?,?, ? , ?, ?, ?", - "INSERT INTO X values(?, ?,?, ? , ?, ?, ?)" - ], - "dialects": [ - "mysql" - ] - }, - { - "name": "string_with_trailing_backslash", - "sql": "SELECT * FROM table WHERE name='foo\\' AND color='blue'", - "obfuscated": [ - "SELECT * FROM table WHERE name=?", - "SELECT * FROM table WHERE name=? AND color=?" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "comments": [ - "If backslashes are being ignored in single-quoted strings", - "(standard_conforming_strings=on in PostgreSQL, or NO_BACKSLASH_ESCAPES is on", - "in MySQL), then this is valid SQL." - ] - }, - { - "name": "string_with_trailing_escaped_backslash.mysql", - "obfuscated": [ - "SELECT * FROM table WHERE foo=?" - ], - "dialects": [ - "mysql" - ], - "sql": "SELECT * FROM table WHERE foo=\"this string ends with a backslash\\\\\"" - }, - { - "name": "string_with_trailing_escaped_backslash_single_quoted", - "obfuscated": [ - "SELECT * FROM table WHERE foo=?" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "SELECT * FROM table WHERE foo='this string ends with a backslash\\\\'" - }, - { - "name": "string_with_trailing_escaped_quote", - "sql": "SELECT * FROM table WHERE name='foo\\'' AND color='blue'", - "obfuscated": [ - "SELECT * FROM table WHERE name=?", - "SELECT * FROM table WHERE name=? AND color=?" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ] - }, - { - "name": "string_with_twin_single_quotes", - "obfuscated": [ - "INSERT INTO X values(?, ?,?, ? , ?)" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "sql": "INSERT INTO X values('', 'a''b c',0, 1 , 'd''e f''s h')" - }, - { - "name": "pathological/end_of_line_comments_with_quotes", - "sql": "SELECT * FROM t WHERE -- '\n bar='baz' -- '", - "obfuscated": [ - "SELECT * FROM t WHERE ?\n bar=? ?", - "SELECT * FROM t WHERE ?" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "pathological": true - }, - { - "name": "pathological/mixed_comments_and_quotes", - "sql": "SELECT * FROM t WHERE /* ' */ \n bar='baz' -- '", - "obfuscated": [ - "SELECT * FROM t WHERE ? \n bar=? ?", - "SELECT * FROM t WHERE ?" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "pathological": true - }, - { - "name": "pathological/mixed_quotes_comments_and_newlines", - "sql": "SELECT * FROM t WHERE -- '\n /* ' */ c2='xxx' /* ' */\n c='x\n xx' -- '", - "obfuscated": [ - "SELECT * FROM t WHERE ?\n ? c2=? ?\n c=? ?", - "SELECT * FROM t WHERE ?" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "pathological": true - }, - { - "name": "pathological/mixed_quotes_end_of_line_comments", - "sql": "SELECT * FROM t WHERE -- '\n c='x\n xx' -- '", - "obfuscated": [ - "SELECT * FROM t WHERE ?\n c=? ?", - "SELECT * FROM t WHERE ?" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "pathological": true - }, - { - "name": "pathological/quote_delimiters_in_comments", - "sql": "SELECT * FROM foo WHERE col='value1' AND /* don't */ col2='value1' /* won't */", - "obfuscated": [ - "SELECT * FROM foo WHERE col=? AND ? col2=? ?", - "SELECT * FROM foo WHERE col=? AND ?" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "pathological": true - }, - { - "name": "malformed/unterminated_double_quoted_string.mysql", - "sql": "SELECT * FROM table WHERE foo='bar' AND baz=\"nothing to see here'", - "dialects": [ - "mysql" - ], - "obfuscated": [ - "?" - ], - "malformed": true - }, - { - "name": "malformed/unterminated_single_quoted_string", - "sql": "SELECT * FROM table WHERE foo='bar' AND baz='nothing to see here", - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "obfuscated": [ - "?" - ], - "malformed": true - }, - { - "name": "dollar_quotes", - "sql": "SELECT * FROM \"foo\" WHERE \"foo\" = $a$dollar quotes can be $b$nested$b$$a$ and bar = 'baz'", - "obfuscated": [ - "SELECT * FROM \"foo\" WHERE \"foo\" = ? and bar = ?" - ], - "dialects": [ - "postgres" - ] - }, - { - "name": "variable_substitution_not_mistaken_for_dollar_quotes", - "sql": "INSERT INTO \"foo\" (\"bar\", \"baz\", \"qux\") VALUES ($1, $2, $3) RETURNING \"id\"", - "obfuscated": [ - "INSERT INTO \"foo\" (\"bar\", \"baz\", \"qux\") VALUES ($?, $?, $?) RETURNING \"id\"" - ], - "dialects": [ - "postgres" - ] - }, - { - "name": "non_quote_escape", - "sql": "select * from foo where bar = 'some\\tthing' and baz = 10", - "obfuscated": [ - "select * from foo where bar = ? and baz = ?" - ], - "dialects": [ - "mssql", - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ] - }, - { - "name": "end_of_string_backslash_and_line_comment_with_quite", - "sql": "select * from users where user = 'user1\\' password = 'hunter 2' -- ->don't count this quote", - "obfuscated": [ - "select * from users where user = ?" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ], - "pathological": true - }, - { - "name": "oracle_bracket_quote", - "sql": "select * from foo where bar=q'[baz's]' and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "oracle" - ] - }, - { - "name": "oracle_brace_quote", - "sql": "select * from foo where bar=q'{baz's}' and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "oracle" - ] - }, - { - "name": "oracle_angle_quote", - "sql": "select * from foo where bar=q'' and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "oracle" - ] - }, - { - "name": "oracle_paren_quote", - "sql": "select * from foo where bar=q'(baz's)' and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "oracle" - ] - }, - { - "name": "cassandra_blobs", - "sql": "select * from foo where bar=0xabcdef123 and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "cassandra", - "sqlite" - ] - }, - { - "name": "hex_literals", - "sql": "select * from foo where bar=0x2F and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "mysql", - "cassandra", - "sqlite" - ] - }, - { - "name": "exponential_literals", - "sql": "select * from foo where bar=1.234e-5 and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ] - }, - { - "name": "negative_integer_literals", - "sql": "select * from foo where bar=-1.234e-5 and x=-5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "mysql", - "postgres", - "oracle", - "cassandra", - "sqlite" - ] - }, - { - "name": "uuid", - "sql": "select * from foo where bar=01234567-89ab-cdef-0123-456789abcdef and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "postgres", - "cassandra" - ] - }, - { - "name": "uuid_with_braces", - "sql": "select * from foo where bar={01234567-89ab-cdef-0123-456789abcdef} and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "postgres" - ] - }, - { - "name": "uuid_no_dashes", - "sql": "select * from foo where bar=0123456789abcdef0123456789abcdef and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "postgres" - ] - }, - { - "name": "uuid_random_dashes", - "sql": "select * from foo where bar={012-345678-9abc-def012345678-9abcdef} and x=5", - "obfuscated": [ - "select * from foo where bar=? and x=?" - ], - "dialects": [ - "postgres" - ] - }, - { - "name": "booleans", - "sql": "select * from truestory where bar=true and x=FALSE", - "obfuscated": [ - "select * from truestory where bar=? and x=?" - ], - "dialects": [ - "mysql", - "postgres", - "cassandra", - "sqlite" - ] - } -] diff --git a/internal/crossagent/cross_agent_tests/sql_parsing.json b/internal/crossagent/cross_agent_tests/sql_parsing.json deleted file mode 100644 index 5590f2f93..000000000 --- a/internal/crossagent/cross_agent_tests/sql_parsing.json +++ /dev/null @@ -1,67 +0,0 @@ -[ - {"input":"SELECT * FROM foobar", "operation":"select", "table":"foobar"}, - {"input":"SELECT F FROM foobar", "operation":"select", "table":"foobar"}, - {"input":"SELECT Ff FROM foobar", "operation":"select", "table":"foobar"}, - {"input":"SELECT I FROM foobar", "operation":"select", "table":"foobar"}, - {"input":"SELECT FROMM FROM foobar", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM foobar WHERE x > y", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM `foobar`", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM `foobar` WHERE x > y", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM database.foobar", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM database.foobar WHERE x > y", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM `database`.foobar", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM `database`.foobar WHERE x > y", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM database.`foobar`", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM database.`foobar` WHERE x > y", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM (foobar)", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM(foobar)", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM (foobar) WHERE x > y", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM (`foobar`)", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM (`foobar`) WHERE x > y", "operation":"select", "table":"foobar"}, - {"input":"SELECT * FROM (SELECT * FROM foobar)", "operation":"select", "table":"(subquery)"}, - {"input":"SELECT * FROM (SELECT * FROM foobar) WHERE x > y", "operation":"select", "table":"(subquery)"}, - {"input":"SELECT xy,zz,y FROM foobar", "operation":"select", "table":"foobar"}, - {"input":"SELECT xy,zz,y FROM foobar ORDER BY zy", "operation":"select", "table":"foobar"}, - {"input":"SELECT xy,zz,y FROM `foobar`", "operation":"select", "table":"foobar"}, - {"input":"SELECT xy,zz,y FROM `foobar` ORDER BY zy", "operation":"select", "table":"foobar"}, - {"input":"SELECT `xy`,`zz`,y FROM foobar", "operation":"select", "table":"foobar"}, - {"input":"SELECT Name FROM `world`.`City` WHERE Population > ?", "operation":"select", "table":"City"}, - {"input":"SELECT frok FROM `world`.`City` WHERE Population > ?", "operation":"select", "table":"City"}, - {"input":"SELECT irom FROM `world`.`City` WHERE Population > ?", "operation":"select", "table":"City"}, - {"input":"SELECT\n\nirom\n\nFROM `world`.`City` WHERE Population > ?", "operation":"select", "table":"City"}, - {"input":"SELECT\n\t irom\n FROM `world`.`City` WHERE Population > ?", "operation":"select", "table":"City"}, - {"input":"SELECT fromm FROM `world`.`City` WHERE Population > ?", "operation":"select", "table":"City"}, - {"input":"SELECT * FROM foo,bar", "operation":"select", "table":"foo"}, - {"input":" \tSELECT * from \"foo\" WHERE a = b", "operation":"select", "table":"foo"}, - {"input":" \tSELECT * \t from \"bar\" WHERE a = b", "operation":"select", "table":"bar"}, - {"input":"SELECT * FROM(SELECT * FROM foobar) WHERE x > y", "operation":"select", "table":"(subquery)"}, - {"input":"SELECT FROM_UNIXTIME() from \"bar\"", "operation":"select", "table":"bar"}, - {"input":"SELECT ffrom from \"frome\"", "operation":"select", "table":"frome"}, - {"input":"SELECT ffrom from (\"frome\")", "operation":"select", "table":"frome"}, - - {"input":"UPDATE abc SET x=1, y=2", "operation":"update", "table":"abc"}, - {"input":"UPDATE\nabc\nSET x=1, y=2", "operation":"update", "table":"abc"}, - {"input":" \tUPDATE abc SET ffrom='iinto'", "operation":"update", "table":"abc"}, - {"input":" \tUPDATE 'abc' SET ffrom='iinto'", "operation":"update", "table":"abc"}, - {"input":" \tUPDATE `abc` SET ffrom='iinto'", "operation":"update", "table":"abc"}, - {"input":" \tUPDATE \"abc\" SET ffrom='iinto'", "operation":"update", "table":"abc"}, - {"input":" \tUPDATE\r\tabc SET ffrom='iinto'", "operation":"update", "table":"abc"}, - - {"input":"INSERT INTO foobar (x,y) VALUES (1,2)", "operation":"insert", "table":"foobar"}, - {"input":"INSERT\nINTO\nfoobar (x,y) VALUES (1,2)", "operation":"insert", "table":"foobar"}, - {"input":"INSERT INTO foobar(x,y) VALUES (1,2)", "operation":"insert", "table":"foobar"}, - - {"input":" /* a */ SELECT * FROM alpha", "operation":"select", "table":"alpha"}, - {"input":"SELECT /* a */ * FROM alpha", "operation":"select", "table":"alpha"}, - {"input":"SELECT\n/* a */ *\nFROM alpha", "operation":"select", "table":"alpha"}, - {"input":"SELECT * /* a */ FROM alpha", "operation":"select", "table":"alpha"}, - {"input":"SELECT * FROM /* a */ alpha", "operation":"select", "table":"alpha"}, - {"input":"/* X */ SELECT /* Y */ foo/**/ FROM /**/alpha/**/", "operation":"select", "table":"alpha"}, - - {"input":"mystoredprocedure'123'", "operation":"other", "table":null}, - {"input":"mystoredprocedure\t'123'", "operation":"other", "table":null}, - {"input":"mystoredprocedure\r'123'", "operation":"other", "table":null}, - {"input":"[mystoredprocedure]123", "operation":"other", "table":null}, - {"input":"\"mystoredprocedure\"abc", "operation":"other", "table":null}, - {"input":"mystoredprocedure", "operation":"other", "table":null} -] diff --git a/internal/crossagent/cross_agent_tests/synthetics/README.md b/internal/crossagent/cross_agent_tests/synthetics/README.md deleted file mode 100644 index 61acbc83c..000000000 --- a/internal/crossagent/cross_agent_tests/synthetics/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Synthetics Tests - -The Synthetics tests are designed to verify that the agent handles valid and invalid Synthetics requests. - -Each test should run a simulated web transaction. A Synthetics HTTP request header is added to the incoming request at the beginning of a web transaction. During the course of the web transaction, an external request is made. And, at the completion of the web transaction, both a Transaction Trace and Transaction Event are recorded. - -Each test then verifies that the correct attributes are added to the Transaction Trace and Transaction Event, and the proper request header is added to the external request when required. Or, in the case of an invalid Synthetics request, that the attributes and request header are **not** added. - -## Name - -| Name | Meaning | -| ---- | ------- | -| `name` | A human-meaningful name for the test case. | - -## Settings - -The `settings` hash contains a number of key-value pairs that the agent will need to use for configuration for the test. - -| Name | Meaning | -| ---- | ------- | -| `agentEncodingKey`| The encoding key used by the agent for deobfuscation of the Synthetics request header. | -| `syntheticsEncodingKey` | The encoding key used by Synthetics to obfuscate the Synthetics request header. In most tests, `agentEncodingKey` and `syntheticsEncodingKey` are the same. | -| `transactionGuid` | The GUID of the simulated transaction. In a non-simulated transaction, this will be randomly generated. But, for testing purposes, you should assign this value as the GUID, since the tests will check for this value to be set in the `nr.guid` attribute of the Transaction Event. | -| `trustedAccountIds` | A list of accounts ids that the agent trusts. If the Synthetics request contains a non-trusted account id, it is an invalid request.| - -## Inputs - -The input for each test is a Synthetics request header. The test fixture file shows both the de-obfuscated version of the payload, as well as the resulting obfuscated version. - -| Name | Meaning | -| ---- | ------- | -| `inputHeaderPayload` | A decoded form of the contents of the `X-NewRelic-Synthetics` request header. | -| `inputObfuscatedHeader` | An obfuscated form of the `X-NewRelic-Synthetics` request header. If you obfuscate `inputHeaderPayload` using the `syntheticsEncodingKey`, this should be the output. | - -## Outputs - -There are three different outputs that are tested for: Transaction Trace, Transaction Event, and External Request Header. - -### outputTransactionTrace - -The `outputTransactionTrace` hash contains three objects: - -| Name | Meaning | -| ---- | ------- | -| `header` | The last field of the transaction sample array should be set to the Synthetics Resource ID for a Synthetics request, and should be set to `null` if it isn't. (The last field in the array is the 10th element in the header array, but is `header[9]` in zero-based array notation, so the key name is `field_9`.) | -| `expectedIntrinsics` | A set of key-value pairs that represent the attributes that should be set in the intrinsics section of the Transaction Trace. **Note**: If the agent has not implemented the Agent Attributes spec, then the agent should save the attributes in the `Custom` section, and the attribute names should have 'nr.' prepended to them. Read the spec for details. For agents in this situation, they will need to adjust the expected output of the tests accordingly. | -| `nonExpectedIntrinsics` | An array of names that represent the attributes that should **not** be set in the intrinsics section of the Transaction Trace.| - -### outputTransactionEvent - -The `outputTransactionEvent` hash contains two objects: - -| Name | Meaning | -| ---- | ------- | -| `expectedAttributes` | A set of key-value pairs that represent the attributes that should be set in the `Intrinsic` hash of the Transaction Event. | -| `nonExpectedAttributes` | An array of names that represent the attributes that should **not** be set in the `Intrinsic` hash of the Transaction Event. | - -### outputExternalRequestHeader - -The `outputExternalRequestHeader` hash contains two objects: - -| Name | Meaning | -| ---- | ------- | -| `expectedHeader` | The outbound header that should be added to external requests (similar to the CAT header), when the original request was made from a valid Synthetics request. | -| `nonExpectedHeader` | The outbound header that should **not** be added to external requests, when the original request was made from a non-Synthetics request. | diff --git a/internal/crossagent/cross_agent_tests/synthetics/synthetics.json b/internal/crossagent/cross_agent_tests/synthetics/synthetics.json deleted file mode 100644 index 6a429fe42..000000000 --- a/internal/crossagent/cross_agent_tests/synthetics/synthetics.json +++ /dev/null @@ -1,317 +0,0 @@ -[ - { - "name": "valid_synthetics_request", - "settings": { - "agentEncodingKey": "1234567890123456789012345678901234567890", - "syntheticsEncodingKey": "1234567890123456789012345678901234567890", - "transactionGuid": "9323dc260548ed0e", - "trustedAccountIds": [ - 444 - ] - }, - "inputHeaderPayload": [ - 1, - 444, - "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm" - ], - "inputObfuscatedHeader": { - "X-NewRelic-Synthetics": "agMfAAECGxpLQkNAQUZHG0VKS0IcAwEHARtFSktCHEBBRkdERUpLQkNAQRYZFF1SU1pbWFkZX1xdUhQBAwEHGV9cXVIUWltYWV5fXF1SU1pbEB8WWFtaVVRdXB9eWVhbGgkLAwUfXllYWxpVVF1cX15ZWFtaVVQSbA==" - }, - "outputTransactionTrace": { - "header": { - "field_9": "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr" - }, - "expectedIntrinsics": { - "synthetics_resource_id": "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "synthetics_job_id": "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "synthetics_monitor_id": "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm" - }, - "nonExpectedIntrinsics": [] - }, - "outputTransactionEvent": { - "expectedAttributes": { - "nr.guid": "9323dc260548ed0e", - "nr.syntheticsResourceId": "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "nr.syntheticsJobId": "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "nr.syntheticsMonitorId": "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm" - }, - "nonExpectedAttributes": [] - }, - "outputExternalRequestHeader": { - "expectedHeader": { - "X-NewRelic-Synthetics": "agMfAAECGxpLQkNAQUZHG0VKS0IcAwEHARtFSktCHEBBRkdERUpLQkNAQRYZFF1SU1pbWFkZX1xdUhQBAwEHGV9cXVIUWltYWV5fXF1SU1pbEB8WWFtaVVRdXB9eWVhbGgkLAwUfXllYWxpVVF1cX15ZWFtaVVQSbA==" - }, - "nonExpectedHeader": [] - } - }, - { - "name": "non_synthetics_request", - "settings": { - "agentEncodingKey": "1234567890123456789012345678901234567890", - "syntheticsEncodingKey": "1234567890123456789012345678901234567890", - "transactionGuid": "9323dc260548ed0e", - "trustedAccountIds": [ - 444 - ] - }, - "inputHeaderPayload": [], - "inputObfuscatedHeader": {}, - "outputTransactionTrace": { - "header": { - "field_9": null - }, - "expectedIntrinsics": {}, - "nonExpectedIntrinsics": [ - "synthetics_resource_id", - "synthetics_job_id", - "synthetics_monitor_id" - ] - }, - "outputTransactionEvent": { - "expectedAttributes": {}, - "nonExpectedAttributes": [ - "nr.syntheticsResourceId", - "nr.syntheticsJobId", - "nr.syntheticsMonitorId" - ] - }, - "outputExternalRequestHeader": { - "expectedHeader": {}, - "nonExpectedHeader": [ - "X-NewRelic-Synthetics" - ] - } - }, - { - "name": "invalid_synthetics_request_unsupported_version", - "settings": { - "agentEncodingKey": "1234567890123456789012345678901234567890", - "syntheticsEncodingKey": "1234567890123456789012345678901234567890", - "transactionGuid": "9323dc260548ed0e", - "trustedAccountIds": [ - 444 - ] - }, - "inputHeaderPayload": [ - 777, - 444, - "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm" - ], - "inputObfuscatedHeader": { - "X-NewRelic-Synthetics": "agUEAxkCAwwVEkNAQUZHREUVS0JDQB4FBwUDFUtCQ0AeRkdERUpLQkNAQUZHFBsaU1pbWFleXxtdUlNaHAMBBwEbXVJTWhxYWV5fXF1SU1pbWFkWGRRaVVRdXF9eGVhbWlUUAQMBBxlYW1pVFF1cX15ZWFtaVVRdXBBu" - }, - "outputTransactionTrace": { - "header": { - "field_9": null - }, - "expectedIntrinsics": {}, - "nonExpectedIntrinsics": [ - "synthetics_resource_id", - "synthetics_job_id", - "synthetics_monitor_id" - ] - }, - "outputTransactionEvent": { - "expectedAttributes": {}, - "nonExpectedAttributes": [ - "nr.syntheticsResourceId", - "nr.syntheticsJobId", - "nr.syntheticsMonitorId" - ] - }, - "outputExternalRequestHeader": { - "expectedHeader": {}, - "nonExpectedHeader": [ - "X-NewRelic-Synthetics" - ] - } - }, - { - "name": "invalid_synthetics_request_untrusted_account_id", - "settings": { - "agentEncodingKey": "1234567890123456789012345678901234567890", - "syntheticsEncodingKey": "1234567890123456789012345678901234567890", - "transactionGuid": "9323dc260548ed0e", - "trustedAccountIds": [ - 444 - ] - }, - "inputHeaderPayload": [ - 1, - 999, - "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm" - ], - "inputObfuscatedHeader": { - "X-NewRelic-Synthetics": "agMfDQwPGxpLQkNAQUZHG0VKS0IcAwEHARtFSktCHEBBRkdERUpLQkNAQRYZFF1SU1pbWFkZX1xdUhQBAwEHGV9cXVIUWltYWV5fXF1SU1pbEB8WWFtaVVRdXB9eWVhbGgkLAwUfXllYWxpVVF1cX15ZWFtaVVQSbA==" - }, - "outputTransactionTrace": { - "header": { - "field_9": null - }, - "expectedIntrinsics": {}, - "nonExpectedIntrinsics": [ - "synthetics_resource_id", - "synthetics_job_id", - "synthetics_monitor_id" - ] - }, - "outputTransactionEvent": { - "expectedAttributes": {}, - "nonExpectedAttributes": [ - "nr.syntheticsResourceId", - "nr.syntheticsJobId", - "nr.syntheticsMonitorId" - ] - }, - "outputExternalRequestHeader": { - "expectedHeader": {}, - "nonExpectedHeader": [ - "X-NewRelic-Synthetics" - ] - } - }, - { - "name": "invalid_synthetics_request_mismatched_encoding_key", - "settings": { - "agentEncodingKey": "0000000000000000000000000000000000000000", - "syntheticsEncodingKey": "1234567890123456789012345678901234567890", - "transactionGuid": "9323dc260548ed0e", - "trustedAccountIds": [ - 444 - ] - }, - "inputHeaderPayload": [ - 1, - 444, - "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm" - ], - "inputObfuscatedHeader": { - "X-NewRelic-Synthetics": "agMfAAECGxpLQkNAQUZHG0VKS0IcAwEHARtFSktCHEBBRkdERUpLQkNAQRYZFF1SU1pbWFkZX1xdUhQBAwEHGV9cXVIUWltYWV5fXF1SU1pbEB8WWFtaVVRdXB9eWVhbGgkLAwUfXllYWxpVVF1cX15ZWFtaVVQSbA==" - }, - "outputTransactionTrace": { - "header": { - "field_9": null - }, - "expectedIntrinsics": {}, - "nonExpectedIntrinsics": [ - "synthetics_resource_id", - "synthetics_job_id", - "synthetics_monitor_id" - ] - }, - "outputTransactionEvent": { - "expectedAttributes": {}, - "nonExpectedAttributes": [ - "nr.syntheticsResourceId", - "nr.syntheticsJobId", - "nr.syntheticsMonitorId" - ] - }, - "outputExternalRequestHeader": { - "expectedHeader": {}, - "nonExpectedHeader": [ - "X-NewRelic-Synthetics" - ] - } - }, - { - "name": "invalid_synthetics_request_too_few_header_elements", - "settings": { - "agentEncodingKey": "1234567890123456789012345678901234567890", - "syntheticsEncodingKey": "1234567890123456789012345678901234567890", - "transactionGuid": "9323dc260548ed0e", - "trustedAccountIds": [ - 444 - ] - }, - "inputHeaderPayload": [ - 1, - 444, - "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj" - ], - "inputObfuscatedHeader": { - "X-NewRelic-Synthetics": "agMfAAECGxpLQkNAQUZHG0VKS0IcAwEHARtFSktCHEBBRkdERUpLQkNAQRYZFF1SU1pbWFkZX1xdUhQBAwEHGV9cXVIUWltYWV5fXF1SU1pbEG4=" - }, - "outputTransactionTrace": { - "header": { - "field_9": null - }, - "expectedIntrinsics": {}, - "nonExpectedIntrinsics": [ - "synthetics_resource_id", - "synthetics_job_id", - "synthetics_monitor_id" - ] - }, - "outputTransactionEvent": { - "expectedAttributes": {}, - "nonExpectedAttributes": [ - "nr.syntheticsResourceId", - "nr.syntheticsJobId", - "nr.syntheticsMonitorId" - ] - }, - "outputExternalRequestHeader": { - "expectedHeader": {}, - "nonExpectedHeader": [ - "X-NewRelic-Synthetics" - ] - } - }, - { - "name": "invalid_synthetics_request_too_many_header_elements", - "settings": { - "agentEncodingKey": "1234567890123456789012345678901234567890", - "syntheticsEncodingKey": "1234567890123456789012345678901234567890", - "transactionGuid": "9323dc260548ed0e", - "trustedAccountIds": [ - 444 - ] - }, - "inputHeaderPayload": [ - 1, - 444, - "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm", - "this doesn't belong here" - ], - "inputObfuscatedHeader": { - "X-NewRelic-Synthetics": "agMfAAECGxpLQkNAQUZHG0VKS0IcAwEHARtFSktCHEBBRkdERUpLQkNAQRYZFF1SU1pbWFkZX1xdUhQBAwEHGV9cXVIUWltYWV5fXF1SU1pbEB8WWFtaVVRdXB9eWVhbGgkLAwUfXllYWxpVVF1cX15ZWFtaVVQSHRBHXFxFF1xWVUJcFEAVVFJUVl5WEltRR1MVZQ==" - }, - "outputTransactionTrace": { - "header": { - "field_9": null - }, - "expectedIntrinsics": {}, - "nonExpectedIntrinsics": [ - "synthetics_resource_id", - "synthetics_job_id", - "synthetics_monitor_id" - ] - }, - "outputTransactionEvent": { - "expectedAttributes": {}, - "nonExpectedAttributes": [ - "nr.syntheticsResourceId", - "nr.syntheticsJobId", - "nr.syntheticsMonitorId" - ] - }, - "outputExternalRequestHeader": { - "expectedHeader": {}, - "nonExpectedHeader": [ - "X-NewRelic-Synthetics" - ] - } - } -] diff --git a/internal/crossagent/cross_agent_tests/transaction_segment_terms.json b/internal/crossagent/cross_agent_tests/transaction_segment_terms.json deleted file mode 100644 index 3caf5d097..000000000 --- a/internal/crossagent/cross_agent_tests/transaction_segment_terms.json +++ /dev/null @@ -1,389 +0,0 @@ -[ - { - "testname": "basic", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Custom", - "terms": ["one", "two", "three"] - }, - { - "prefix": "WebTransaction/Uri", - "terms": ["seven", "eight", "nine"] - } - ], - "tests": [ - { - "input": "WebTransaction/Uri/one/two/seven/user/nine/account", - "expected": "WebTransaction/Uri/*/seven/*/nine/*" - }, - { - "input": "WebTransaction/Custom/one/two/seven/user/nine/account", - "expected": "WebTransaction/Custom/one/two/*" - }, - { - "input": "WebTransaction/Other/one/two/foo/bar", - "expected": "WebTransaction/Other/one/two/foo/bar" - } - ] - }, - { - "testname": "prefix_with_trailing_slash", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Custom/", - "terms": ["a", "b"] - } - ], - "tests": [ - { - "input": "WebTransaction/Custom/a/b/c", - "expected": "WebTransaction/Custom/a/b/*" - }, - { - "input": "WebTransaction/Other/a/b/c", - "expected": "WebTransaction/Other/a/b/c" - } - ] - }, - { - "testname": "prefix_with_trailing_spaces_and_then_slash", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Custom /", - "terms": ["a", "b"] - } - ], - "tests": [ - { - "input": "WebTransaction/Custom /a/b/c", - "expected": "WebTransaction/Custom /a/b/*" - }, - { - "input": "WebTransaction/Custom /a/b/c", - "expected": "WebTransaction/Custom /a/b/c" - }, - { - "input": "WebTransaction/Custom/a/b/c", - "expected": "WebTransaction/Custom/a/b/c" - } - ] - }, - { - "testname": "prefix_with_trailing_spaces", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Custom ", - "terms": ["a", "b"] - } - ], - "tests": [ - { - "input": "WebTransaction/Custom /a/b/c", - "expected": "WebTransaction/Custom /a/b/*" - }, - { - "input": "WebTransaction/Custom /a/b/c", - "expected": "WebTransaction/Custom /a/b/c" - }, - { - "input": "WebTransaction/Custom/a/b/c", - "expected": "WebTransaction/Custom/a/b/c" - } - ] - }, - { - "testname": "overlapping_prefix_last_one_only_applied", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Foo", - "terms": ["one", "two", "three"] - }, - { - "prefix": "WebTransaction/Foo", - "terms": ["one", "two", "zero"] - } - ], - "tests": [ - { - "input": "WebTransaction/Foo/zero/one/two/three/four", - "expected": "WebTransaction/Foo/zero/one/two/*" - } - ] - }, - { - "testname": "terms_are_order_independent", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Foo", - "terms": ["one", "two", "three"] - } - ], - "tests": [ - { - "input": "WebTransaction/Foo/bar/one/three/two", - "expected": "WebTransaction/Foo/*/one/three/two" - }, - { - "input": "WebTransaction/Foo/three/one/one/two/three", - "expected": "WebTransaction/Foo/three/one/one/two/three" - } - ] - }, - { - "testname": "invalid_rule_not_enough_prefix_segments", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction", - "terms": ["one", "two"] - } - ], - "tests": [ - { - "input": "WebTransaction/Foo/bar/one/three/two", - "expected": "WebTransaction/Foo/bar/one/three/two" - }, - { - "input": "WebTransaction/Foo/three/one/one/two/three", - "expected": "WebTransaction/Foo/three/one/one/two/three" - } - ] - }, - { - "testname": "invalid_rule_not_enough_prefix_segments_ending_in_slash", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/", - "terms": ["one", "two"] - } - ], - "tests": [ - { - "input": "WebTransaction/Foo/bar/one/three/two", - "expected": "WebTransaction/Foo/bar/one/three/two" - }, - { - "input": "WebTransaction/Foo/three/one/one/two/three", - "expected": "WebTransaction/Foo/three/one/one/two/three" - } - ] - }, - { - "testname": "invalid_rule_too_many_prefix_segments", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Foo/bar", - "terms": ["one", "two"] - } - ], - "tests": [ - { - "input": "WebTransaction/Foo/bar/one/three/two", - "expected": "WebTransaction/Foo/bar/one/three/two" - }, - { - "input": "WebTransaction/Foo/three/one/one/two/three", - "expected": "WebTransaction/Foo/three/one/one/two/three" - } - ] - }, - { - "testname": "invalid_rule_prefix_with_trailing_slash_and_then_space", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Custom/ ", - "terms": ["a", "b"] - } - ], - "tests": [ - { - "input": "WebTransaction/Custom/a/b/c", - "expected": "WebTransaction/Custom/a/b/c" - } - ] - }, - { - "testname": "invalid_rule_prefix_with_multiple_trailing_slashes", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Custom////", - "terms": ["a", "b"] - } - ], - "tests": [ - { - "input": "WebTransaction/Custom/a/b/c", - "expected": "WebTransaction/Custom/a/b/c" - } - ] - }, - { - "testname": "invalid_rule_null_prefix", - "transaction_segment_terms": [ - { - "terms": ["one", "two", "three"] - } - ], - "tests": [ - { - "input": "WebTransaction/Custom/one/two/seven/user/nine/account", - "expected": "WebTransaction/Custom/one/two/seven/user/nine/account" - } - ] - }, - { - "testname": "invalid_rule_null_terms", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Custom" - } - ], - "tests": [ - { - "input": "WebTransaction/Custom/one/two/seven/user/nine/account", - "expected": "WebTransaction/Custom/one/two/seven/user/nine/account" - } - ] - }, - { - "testname": "empty_terms", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Custom", - "terms": [] - } - ], - "tests": [ - { - "input": "WebTransaction/Custom/one/two/seven/user/nine/account", - "expected": "WebTransaction/Custom/*" - }, - { - "input": "WebTransaction/Custom/", - "expected": "WebTransaction/Custom/" - }, - { - "input": "WebTransaction/Custom", - "expected": "WebTransaction/Custom" - } - ] - }, - { - "testname": "two_segment_transaction_name", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Foo", - "terms": ["a", "b", "c"] - } - ], - "tests": [ - { - "input": "WebTransaction/Foo", - "expected": "WebTransaction/Foo" - } - ] - }, - { - "testname": "two_segment_transaction_name_with_trailing_slash", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Foo", - "terms": ["a", "b", "c"] - } - ], - "tests": [ - { - "input": "WebTransaction/Foo/", - "expected": "WebTransaction/Foo/" - } - ] - }, - { - "testname": "transaction_segment_with_adjacent_slashes", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Foo", - "terms": ["a", "b", "c"] - } - ], - "tests": [ - { - "input": "WebTransaction/Foo///a/b///c/d/", - "expected": "WebTransaction/Foo/*/a/b/*/c/*" - }, - { - "input": "WebTransaction/Foo///a/b///c///", - "expected": "WebTransaction/Foo/*/a/b/*/c/*" - } - ] - }, - { - "testname": "transaction_name_with_single_segment", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Foo", - "terms": ["a", "b", "c"] - } - ], - "tests": [ - { - "input": "WebTransaction", - "expected": "WebTransaction" - } - ] - }, - { - "testname": "prefix_must_match_first_two_segments", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Zip", - "terms": ["a", "b"] - } - ], - "tests": [ - { - "input": "WebTransaction/Zip/a/b/c", - "expected": "WebTransaction/Zip/a/b/*" - }, - { - "input": "WebTransaction/ZipZap/a/b/c", - "expected": "WebTransaction/ZipZap/a/b/c" - } - ] - }, - { - "testname": "one_bad_rule_does_not_scrap_all_rules", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/MissingTerms" - }, - { - "prefix": "WebTransaction/Uri", - "terms": ["seven", "eight", "nine"] - } - ], - "tests": [ - { - "input": "WebTransaction/Uri/one/two/seven/user/nine/account", - "expected": "WebTransaction/Uri/*/seven/*/nine/*" - } - ] - }, - { - "testname": "one_bad_matching_rule_at_end_does_not_scrap_other_matching_rules", - "transaction_segment_terms": [ - { - "prefix": "WebTransaction/Uri", - "terms": ["seven", "eight", "nine"] - }, - { - "prefix": "WebTransaction/Uri" - } - ], - "tests": [ - { - "input": "WebTransaction/Uri/one/two/seven/user/nine/account", - "expected": "WebTransaction/Uri/*/seven/*/nine/*" - } - ] - } -] diff --git a/internal/crossagent/cross_agent_tests/url_clean.json b/internal/crossagent/cross_agent_tests/url_clean.json deleted file mode 100644 index 3f370c379..000000000 --- a/internal/crossagent/cross_agent_tests/url_clean.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - {"testname":"only domain", "expected":"domain.com", "input":"domain.com"}, - {"testname":"domain path", "expected":"domain.com/a/b/c", "input":"domain.com/a/b/c"}, - {"testname":"port", "expected":"domain.com:1234/a/b/c", "input":"domain.com:1234/a/b/c"}, - {"testname":"user", "expected":"domain.com/a/b/c", "input":"user@domain.com/a/b/c"}, - {"testname":"user pw", "expected":"domain.com/a/b/c", "input":"user:password@domain.com/a/b/c"}, - {"testname":"scheme domain", "expected":"p://domain.com", "input":"p://domain.com"}, - {"testname":"scheme path", "expected":"p://domain.com/a/b/c", "input":"p://domain.com/a/b/c"}, - {"testname":"scheme port", "expected":"p://domain.com:1234/a/b/c", "input":"p://domain.com:1234/a/b/c"}, - {"testname":"scheme user", "expected":"p://domain.com/a/b/c", "input":"p://user@domain.com/a/b/c"}, - {"testname":"scheme user pw", "expected":"p://domain.com/a/b/c", "input":"p://user:password@domain.com/a/b/c"}, - {"testname":"fragment", "expected":"p://domain.com/a/b/c", "input":"p://user:password@domain.com/a/b/c#fragment"}, - {"testname":"query", "expected":"p://domain.com/a/b/c", "input":"p://user:password@domain.com/a/b/c?query=yes"}, - {"testname":"semi-colon", "expected":"p://domain.com/a/b/c", "input":"p://user:password@domain.com/a/b/c;semi=yes"} -] diff --git a/internal/crossagent/cross_agent_tests/url_domain_extraction.json b/internal/crossagent/cross_agent_tests/url_domain_extraction.json deleted file mode 100644 index 9ac22a33c..000000000 --- a/internal/crossagent/cross_agent_tests/url_domain_extraction.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - {"expected":"domain", "input":"scheme://domain:port/path?query_string#fragment_id"}, - {"expected":"0.0.0.0", "input":"scheme://0.0.0.0:port/path?query_string#fragment_id"}, - {"expected":"localhost", "input":"scheme://localhost:port/path?query_string#fragment_id"}, - {"expected":"127.0.0.1", "input":"scheme://127.0.0.1:port/path?query_string#fragment_id"}, - - {"expected":"0.0.0.0", "input":"scheme://0.0.0.0:8087/path?query_string#fragment_id"}, - {"expected":"localhost", "input":"scheme://localhost:8087/path?query_string#fragment_id"}, - {"expected":"127.0.0.1", "input":"scheme://127.0.0.1:8087/path?query_string#fragment_id"}, - - {"expected":"a.b", "input":"a.b"}, - {"expected":"a.b", "input":"user@a.b"}, - {"expected":"a.b", "input":"user:pass@a.b"}, - {"expected":"a.b", "input":"a.b:123"}, - {"expected":"a.b", "input":"user@a.b:123"}, - {"expected":"a.b", "input":"user:pass@a.b:123"}, - {"expected":"a.b", "input":"a.b/c/d?e=f"}, - {"expected":"a.b", "input":"user@a.b/c/d?e=f"}, - {"expected":"a.b", "input":"user:pass@a.b/c/d?e=f"}, - {"expected":"a.b", "input":"a.b:123/c/d?e=f"}, - {"expected":"a.b", "input":"user@a.b:123/c/d?e=f"}, - {"expected":"a.b", "input":"user:pass@a.b:123/c/d?e=f"}, - {"expected":"a.b", "input":"p://a.b"}, - {"expected":"a.b", "input":"p://user@a.b"}, - {"expected":"a.b", "input":"p://user:pass@a.b"}, - {"expected":"a.b", "input":"p://a.b:123"}, - {"expected":"a.b", "input":"p://user@a.b:123"}, - {"expected":"a.b", "input":"p://user:pass@a.b:123"}, - {"expected":"a.b", "input":"p://a.b/c/d?e=f"}, - {"expected":"a.b", "input":"p://user@a.b/c/d?e=f"}, - {"expected":"a.b", "input":"p://user:pass@a.b/c/d?e=f"}, - {"expected":"a.b", "input":"p://a.b:123/c/d?e=f"}, - {"expected":"a.b", "input":"p://user@a.b:123/c/d?e=f"}, - {"expected":"a.b", "input":"p://user:pass@a.b:123/c/d?e=f"} -] diff --git a/internal/crossagent/cross_agent_tests/utilization/README.md b/internal/crossagent/cross_agent_tests/utilization/README.md deleted file mode 100644 index a51057036..000000000 --- a/internal/crossagent/cross_agent_tests/utilization/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# The Utilization Tests - -The Utilization tests ensure that the appropriate information is being gathered for pricing. It is centered around ensuring that the JSON generated by all agents is correct. Each JSON block is a test case, with potentially the following fields: - - `testname`: The name of the test. - - `input_total_ram_mib`: The total ram number calculated by the agent. - - `input_logical_processors`: The number of logical processors calculated by the agent. - - `input_hostname`: The `hostname` calculated by the agent. - - `input_full_hostname`: The `full_hostname` calculated by the agent. - - `input_ip_address`: A string array containing all the values in `ip_address` calculated by the agent. - - `input_aws_id`: The aws `id` determined by the agent. - - `input_aws_type`: The aws `type` determined by the agent. - - `input_aws_zone`: The aws `zone` determined by the agent. - - `input_environment_variables`: Any environment variables which have been set. - - `expected_output_json`: The expected JSON output from the agent for the `utilization` hash. - -New fields for Google Cloud Platform (gcp), Pivotal Cloud Foundry (pcf), and Azure added as of [Utilization spec version 8](https://source.datanerd.us/agents/agent-specs/blob/master/Utilization.md): - - `input_gcp_id`: The gcp `id` determined by the agent. - - `input_gcp_type`: The gcp `machineType` determined by the agent. - - `input_gcp_name`: The gcp `name` determined by the agent. - - `input_gcp_zone`: The gcp `zone` determined by the agent. - - `input_pcf_guid`: The pcf `cf_instance_guid` determined by the agent. - - `input_pcf_ip`: The pcf `cf_instance_ip` determined by the agent. - - `input_pcf_mem_limit`: The pcf `memory_limit` determined by the agent. - - `input_azure_location`: The azure `location` determined by the agent. - - `input_azure_name`: The azure `name` determined by the agent. - - `input_azure_id`: The azure `vmId` determined by the agent. - - `input_azure_size`: The azure `vmSize` determined by the agent. - -Test cases for `boot_id.json` added as of [Utilization spec version 8](https://source.datanerd.us/agents/agent-specs/blob/master/Utilization.md): - - `testname`: The name of the test. - - `input_total_ram_mib`: The total ram number calculated by the agent. - - `input_logical_processors`: The number of logical processors calculated by the agent. - - `input_hostname`: The hostname calculated by the agent. - - `input_boot_id`: The `boot_id` determined by the agent. - - `expected_output_json`: The expected JSON output from the agent for the utilization hash. - - `expected_metrics`: Supportability metrics that are either expected or unexpected in a given case. If the `call_count` is 0 it should be asserted that the Supportability metric was not sent. diff --git a/internal/crossagent/cross_agent_tests/utilization/boot_id.json b/internal/crossagent/cross_agent_tests/utilization/boot_id.json deleted file mode 100644 index 765a5dcf1..000000000 --- a/internal/crossagent/cross_agent_tests/utilization/boot_id.json +++ /dev/null @@ -1,109 +0,0 @@ -[ - { - "testname": "boot_id file not found", - "input_total_ram_mib": 1024, - "input_logical_processors": 8, - "input_hostname": "myhost", - "input_boot_id": null, - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 1024, - "hostname": "myhost" - }, - "expected_metrics": { - "Supportability/utilization/boot_id/error": { - "call_count": 1 - } - } - }, - { - "testname": "valid boot_id, should be 36 characters", - "input_total_ram_mib": 1024, - "input_logical_processors": 8, - "input_hostname": "myhost", - "input_boot_id": "8e84c4ab-943d-46c2-8675-fdf0ab61e1c4", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 1024, - "hostname": "myhost", - "boot_id": "8e84c4ab-943d-46c2-8675-fdf0ab61e1c4" - } - }, - { - "testname": "boot_id too long, should be truncated to 128 characters max", - "input_total_ram_mib": 1024, - "input_logical_processors": 8, - "input_hostname": "myhost", - "input_boot_id": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 1024, - "hostname": "myhost", - "boot_id": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" - }, - "expected_metrics": { - "Supportability/utilization/boot_id/error": { - "call_count": 1 - } - } - }, - { - "testname": "boot_id too short, should be reported as is", - "input_total_ram_mib": 1024, - "input_logical_processors": 8, - "input_hostname": "myhost", - "input_boot_id": "1234", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 1024, - "hostname": "myhost", - "boot_id": "1234" - }, - "expected_metrics": { - "Supportability/utilization/boot_id/error": { - "call_count": 1 - } - } - }, - { - "testname": "boot_id too short with non-alphanumeric characters, should be reported as is", - "input_total_ram_mib": 1024, - "input_logical_processors": 8, - "input_hostname": "myhost", - "input_boot_id": "", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 1024, - "hostname": "myhost", - "boot_id": "" - }, - "expected_metrics": { - "Supportability/utilization/boot_id/error": { - "call_count": 1 - } - } - }, - { - "testname": "boot_id file empty", - "input_total_ram_mib": 1024, - "input_logical_processors": 8, - "input_hostname": "myhost", - "input_boot_id": "", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 1024, - "hostname": "myhost" - }, - "expected_metrics": { - "Supportability/utilization/boot_id/error": { - "call_count": 1 - } - } - } -] diff --git a/internal/crossagent/cross_agent_tests/utilization/utilization_json.json b/internal/crossagent/cross_agent_tests/utilization/utilization_json.json deleted file mode 100644 index a5ed101bc..000000000 --- a/internal/crossagent/cross_agent_tests/utilization/utilization_json.json +++ /dev/null @@ -1,375 +0,0 @@ -[ - { - "testname": "only agent derived data", - "input_total_ram_mib": 1024, - "input_logical_processors": 8, - "input_hostname": "myhost", - "input_full_hostname": "myhost.com", - "input_ip_address": ["1.2.3.4", "1.2.3.5"], - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 1024, - "hostname": "myhost", - "full_hostname": "myhost.com", - "ip_address": ["1.2.3.4", "1.2.3.5"] - } - }, - { - "testname": "only agent derived but bad data", - "input_total_ram_mib": null, - "input_logical_processors": null, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["2001:0db8:0000:0042:0000:8a2e:0370:7334"], - "expected_output_json": { - "metadata_version": 5, - "logical_processors": null, - "total_ram_mib": null, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["2001:0db8:0000:0042:0000:8a2e:0370:7334"] - } - }, - { - "testname": "agent derived null and some environment variables", - "input_total_ram_mib": null, - "input_logical_processors": null, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["::FFFF:129.144.52.38"], - "input_environment_variables": { - "NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS": 8, - "NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB": 2048 - }, - "expected_output_json": { - "metadata_version": 5, - "logical_processors": null, - "total_ram_mib": null, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["::FFFF:129.144.52.38"], - "config": { - "logical_processors": 8, - "total_ram_mib": 2048 - } - } - }, - { - "testname": "all environment variables", - "input_total_ram_mib": 1, - "input_logical_processors": 2, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["8.8.8.8"], - "input_environment_variables": { - "NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS": 16, - "NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB": 4096, - "NEW_RELIC_UTILIZATION_BILLING_HOSTNAME": "localhost" - }, - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 2, - "total_ram_mib": 1, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["8.8.8.8"], - "config": { - "logical_processors": 16, - "total_ram_mib": 4096, - "hostname": "localhost" - } - } - }, - { - "testname": "all environment variables with error in processors", - "input_total_ram_mib": 1024, - "input_logical_processors": 4, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["2001:0db8:0000:0042:0000:8a2e:0370:7334"], - "input_environment_variables": { - "NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS": "abc", - "NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB": 4096, - "NEW_RELIC_UTILIZATION_BILLING_HOSTNAME": "localhost" - }, - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 4, - "total_ram_mib": 1024, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["2001:0db8:0000:0042:0000:8a2e:0370:7334"], - "config": { - "total_ram_mib": 4096, - "hostname": "localhost" - } - } - }, - { - "testname": "all environment variables with error in ram", - "input_total_ram_mib": 1024, - "input_logical_processors": 4, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["2001:0db8:0000:0042:0000:8a2e:0370:7334"], - "input_environment_variables": { - "NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS": 8, - "NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB": "notgood", - "NEW_RELIC_UTILIZATION_BILLING_HOSTNAME": "localhost" - }, - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 4, - "total_ram_mib": 1024, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["2001:0db8:0000:0042:0000:8a2e:0370:7334"], - "config": { - "logical_processors": 8, - "hostname": "localhost" - } - } - }, - { - "testname": "only agent derived data with aws", - "input_total_ram_mib": 2048, - "input_logical_processors": 8, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["1.2.3.4"], - "input_aws_id": "8BADFOOD", - "input_aws_type": "t2.micro", - "input_aws_zone": "us-west-1", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 2048, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["1.2.3.4"], - "vendors": { - "aws": { - "instanceId": "8BADFOOD", - "instanceType": "t2.micro", - "availabilityZone": "us-west-1" - } - } - } - }, - { - "testname": "invalid agent derived data with aws", - "input_total_ram_mib": 2048, - "input_logical_processors": 8, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["1.2.3.4"], - "input_aws_id": null, - "input_aws_type": "t2.micro", - "input_aws_zone": "us-west-1", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 2048, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["1.2.3.4"] - } - }, - { - "testname": "only agent derived data with gcp", - "input_total_ram_mib": 2048, - "input_logical_processors": 8, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["1.2.3.4"], - "input_gcp_id": "3161347020215157000", - "input_gcp_type": "projects/492690098729/machineTypes/custom-1-1024", - "input_gcp_name": "aef-default-20170501t160547-7gh8", - "input_gcp_zone": "projects/492690098729/zones/us-central1-c", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 2048, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["1.2.3.4"], - "vendors": { - "gcp": { - "id": "3161347020215157000", - "machineType": "custom-1-1024", - "name": "aef-default-20170501t160547-7gh8", - "zone": "us-central1-c" - } - } - } - }, - { - "testname": "invalid agent derived data with gcp", - "input_total_ram_mib": 2048, - "input_logical_processors": 8, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["1.2.3.4"], - "input_gcp_id": "3161347020215157000", - "input_gcp_type": "projects/492690098729/machineTypes/custom-1-1024", - "input_gcp_name": null, - "input_gcp_zone": "projects/492690098729/zones/us-central1-c", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 2048, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["1.2.3.4"] - } - }, - { - "testname": "only agent derived data with pcf", - "input_total_ram_mib": 2048, - "input_logical_processors": 8, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["1.2.3.4"], - "input_pcf_guid": "b977d090-83db-4bdb-793a-bb77", - "input_pcf_ip": "10.10.147.130", - "input_pcf_mem_limit": "1024m", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 2048, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["1.2.3.4"], - "vendors": { - "pcf": { - "cf_instance_guid": "b977d090-83db-4bdb-793a-bb77", - "cf_instance_ip": "10.10.147.130", - "memory_limit": "1024m" - } - } - } - }, - { - "testname": "invalid agent derived data with pcf", - "input_total_ram_mib": 2048, - "input_logical_processors": 8, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["1.2.3.4"], - "input_pcf_guid": null, - "input_pcf_ip": "10.10.147.130", - "input_pcf_mem_limit": "1024m", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 2048, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["1.2.3.4"] - } - }, - { - "testname": "only agent derived data with azure", - "input_total_ram_mib": 2048, - "input_logical_processors": 8, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["1.2.3.4"], - "input_azure_location": "CentralUS", - "input_azure_name": "IMDSCanary", - "input_azure_id": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "input_azure_size": "Standard_DS2", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 2048, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["1.2.3.4"], - "vendors": { - "azure": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "Standard_DS2" - } - } - } - }, - { - "testname": "invalid agent derived data with azure", - "input_total_ram_mib": 2048, - "input_logical_processors": 8, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["1.2.3.4"], - "input_azure_location": "CentralUS", - "input_azure_name": "IMDSCanary", - "input_azure_id": null, - "input_azure_size": "Standard_DS2", - "expected_output_json": { - "metadata_version": 5, - "logical_processors": 8, - "total_ram_mib": 2048, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["1.2.3.4"] - } - }, - { - "testname": "kubernetes service host environment variable", - "input_total_ram_mib": null, - "input_logical_processors": null, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["::FFFF:129.144.52.38"], - "input_environment_variables": { - "NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS": 8, - "NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB": 2048, - "KUBERNETES_SERVICE_HOST": "10.96.0.1" - }, - "expected_output_json": { - "metadata_version": 5, - "logical_processors": null, - "total_ram_mib": null, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["::FFFF:129.144.52.38"], - "config": { - "logical_processors": 8, - "total_ram_mib": 2048 - }, - "vendors": { - "kubernetes": { - "kubernetes_service_host": "10.96.0.1" - } - } - } - }, - { - "testname": "only kubernetes service port environment variable", - "input_total_ram_mib": null, - "input_logical_processors": null, - "input_hostname": "myotherhost", - "input_full_hostname": "myotherhost.com", - "input_ip_address": ["::FFFF:129.144.52.38"], - "input_environment_variables": { - "NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS": 8, - "NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB": 2048, - "KUBERNETES_SERVICE_PORT": "8080" - }, - "expected_output_json": { - "metadata_version": 5, - "logical_processors": null, - "total_ram_mib": null, - "hostname": "myotherhost", - "full_hostname": "myotherhost.com", - "ip_address": ["::FFFF:129.144.52.38"], - "config": { - "logical_processors": 8, - "total_ram_mib": 2048 - } - } - } -] diff --git a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/README.md b/internal/crossagent/cross_agent_tests/utilization_vendor_specific/README.md deleted file mode 100644 index d43976338..000000000 --- a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Vendor Specific Utilization Tests - -The Utilization tests ensure that the appropriate information is being gathered for pricing for a particular cloud vendor. It is centered around ensuring that the JSON generated by all agents is correct. Each JSON block is a test case, with potentially the following fields: - - - `testname`: The name of the test. - - `uri`: The API endpoint for the cloud vendor. This contains a response indicating what the expected return from the API is for a given test. - - `expected_vendors_hash`: The vendor hash that should be generated by the agent based on the uri response. - - `expected_metrics`: Supportability metrics that are either expected or unexpected in a given case. If the `call_count` is 0 it should be asserted that the Supportability metric was not sent. - -As of [Metadata version 3](https://source.datanerd.us/agents/agent-specs/blob/c78cddeaa5fa23dce892b8c6da95b9f900636c35/Utilization.md) specs have been added for Azure, Google Cloud Platform, and Pivotal Cloud Foundry in addition to updates for AWS. \ No newline at end of file diff --git a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/aws.json b/internal/crossagent/cross_agent_tests/utilization_vendor_specific/aws.json deleted file mode 100644 index 97bc04771..000000000 --- a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/aws.json +++ /dev/null @@ -1,233 +0,0 @@ -[ - { - "testname": "aws api times out, no vendor hash or supportability metric reported", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": null, - "instanceType": null, - "availabilityZone": null - }, - "timeout": true - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/aws/error": { - "call_count": 0 - } - } - }, - { - "testname": "instance type, instance-id, availability-zone are all happy", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "i-test.19characters", - "instanceType": "test.type", - "availabilityZone": "us-west-2b" - }, - "timeout": false - } - }, - "expected_vendors_hash": { - "aws": { - "instanceId": "i-test.19characters", - "instanceType": "test.type", - "availabilityZone": "us-west-2b" - } - } - }, - { - "testname": "instance type with invalid characters", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "test.id", - "instanceType": "", - "availabilityZone": "us-west-2b" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/aws/error": { - "call_count": 1 - } - } - }, - { - "testname": "instance type too long", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "test.id", - "instanceType": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "availabilityZone": "us-west-2b" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/aws/error": { - "call_count": 1 - } - } - }, - { - "testname": "instance id with invalid characters", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "", - "instanceType": "test.type", - "availabilityZone": "us-west-2b" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/aws/error": { - "call_count": 1 - } - } - }, - { - "testname": "instance id too long", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "instanceType": "test.type", - "availabilityZone": "us-west-2b" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/aws/error": { - "call_count": 1 - } - } - }, - { - "testname": "availability zone with invalid characters", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "test.id", - "instanceType": "test.type", - "availabilityZone": "" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/aws/error": { - "call_count": 1 - } - } - }, - { - "testname": "availability zone too long", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "test.id", - "instanceType": "test.type", - "availabilityZone": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/aws/error": { - "call_count": 1 - } - } - }, - { - "testname": "UTF-8 high codepoints", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "instanceType": "test.type", - "availabilityZone": "us-west-2b" - }, - "timeout": false - } - }, - "expected_vendors_hash": { - "aws": { - "instanceId": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "instanceType": "test.type", - "availabilityZone": "us-west-2b" - } - } - }, - { - "testname": "comma with multibyte characters", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲, 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "instanceType": "test.type", - "availabilityZone": "us-west-2b" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/aws/error": { - "call_count": 1 - } - } - }, - { - "testname": "Exclamation point response", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "bang!", - "instanceType": "test.type", - "availabilityZone": "us-west-2b" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/aws/error": { - "call_count": 1 - } - } - }, - { - "testname": "Valid punctuation in response", - "uri": { - "http://169.254.169.254/2016-09-02/dynamic/instance-identity/document": { - "response": { - "instanceId": "test.id", - "instanceType": "a-b_c.3... and/or 503 867-5309", - "availabilityZone": "us-west-2b" - }, - "timeout": false - } - }, - "expected_vendors_hash": { - "aws": { - "instanceId": "test.id", - "instanceType": "a-b_c.3... and/or 503 867-5309", - "availabilityZone": "us-west-2b" - } - } - } -] diff --git a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/azure.json b/internal/crossagent/cross_agent_tests/utilization_vendor_specific/azure.json deleted file mode 100644 index 0d32c5c67..000000000 --- a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/azure.json +++ /dev/null @@ -1,287 +0,0 @@ -[ - { - "testname": "azure api times out, no vendor hash or supportability metric reported", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": null, - "name": null, - "vmId": null, - "vmSize": null - }, - "timeout": true - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 0 - } - } - }, - { - "testname": "vmId, location, name, vmSize are all happy", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_vendors_hash": { - "azure": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "Standard_DS2" - } - } - }, - { - "testname": "vmSize with invalid characters", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "vmSize too long", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "vmId with invalid characters", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "vmId too long", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "location with invalid characters", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "", - "name": "IMDSCanary", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "location too long", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "name": "IMDSCanary", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "name with invalid characters", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "name too long", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "UTF-8 high codepoints", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_vendors_hash": { - "azure": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "vmSize": "Standard_DS2" - } - } - }, - { - "testname": "comma with multibyte characters", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲, 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "Exclamation point in response", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "Bang!", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "Standard_DS2" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/azure/error": { - "call_count": 1 - } - } - }, - { - "testname": "Valid punctuation in response", - "uri": { - "http://169.254.169.254/metadata/instance/compute?api-version=2017-03-01": { - "response": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "a-b_c.3... and/or 503 867-5309" - }, - "timeout": false - } - }, - "expected_vendors_hash": { - "azure": { - "location": "CentralUS", - "name": "IMDSCanary", - "vmId": "5c08b38e-4d57-4c23-ac45-aca61037f084", - "vmSize": "a-b_c.3... and/or 503 867-5309" - } - } - } -] diff --git a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/gcp.json b/internal/crossagent/cross_agent_tests/utilization_vendor_specific/gcp.json deleted file mode 100644 index 9912790c0..000000000 --- a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/gcp.json +++ /dev/null @@ -1,288 +0,0 @@ -[ - { - "testname": "gcp api times out, no vendor hash or supportability metric reported", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": null, - "machineType": null, - "name": null, - "zone": null - }, - "timeout": true - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 0 - } - } - }, - { - "testname": "machine type, id, zone, name are all happy", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "aef-default-20170501t160547-7gh8", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": { - "gcp": { - "id": "3161347020215157000", - "machineType": "custom-1-1024", - "name": "aef-default-20170501t160547-7gh8", - "zone": "us-central1-c" - } - } - }, - { - "testname": "machine type with invalid characters", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "", - "name": "aef-default-20170501t160547-7gh8", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "machine type too long", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "name": "aef-default-20170501t160547-7gh8", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "id with invalid characters", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": "", - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "aef-default-20170501t160547-7gh8", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "id too long", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "aef-default-20170501t160547-7gh8", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "zone with invalid characters", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "aef-default-20170501t160547-7gh8", - "zone": "" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "zone too long", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "aef-default-20170501t160547-7gh8", - "zone": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "name with invalid characters", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "name too long", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "UTF-8 high codepoints", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": { - "gcp": { - "id": "3161347020215157000", - "machineType": "custom-1-1024", - "name": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "zone": "us-central1-c" - } - } - }, - { - "testname": "comma with multibyte characters", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲, 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "Exclamation point in response", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "Bang!", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/gcp/error": { - "call_count": 1 - } - } - }, - { - "testname": "Valid punctuation in response", - "uri": { - "http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true": { - "response": { - "id": 3161347020215157000, - "machineType": "projects/492690098729/machineTypes/custom-1-1024", - "name": "a-b_c.3... and/or 503 867-5309", - "zone": "projects/492690098729/zones/us-central1-c" - }, - "timeout": false - } - }, - "expected_vendors_hash": { - "gcp": { - "id": "3161347020215157000", - "machineType": "custom-1-1024", - "name": "a-b_c.3... and/or 503 867-5309", - "zone": "us-central1-c" - } - } - } -] diff --git a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/pcf.json b/internal/crossagent/cross_agent_tests/utilization_vendor_specific/pcf.json deleted file mode 100644 index 323dcbab7..000000000 --- a/internal/crossagent/cross_agent_tests/utilization_vendor_specific/pcf.json +++ /dev/null @@ -1,281 +0,0 @@ -[ - { - "testname": "routine failure to retrieve environment variables, no vendor hash or supportability metric reported", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": null, - "timeout": true - }, - "CF_INSTANCE_IP": { - "response": null, - "timeout": true - }, - "MEMORY_LIMIT": { - "response": null, - "timeout": true - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/pcf/error": { - "call_count": 0 - } - } - }, - { - "testname": "cf_instance_guid, cf_instance_ip, memory_limit are all happy", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "fd326c0e-847e-47a1-65cc-45f6", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "10.10.149.48", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "1024m", - "timeout": false - } - }, - "expected_vendors_hash": { - "pcf": { - "cf_instance_guid": "fd326c0e-847e-47a1-65cc-45f6", - "cf_instance_ip": "10.10.149.48", - "memory_limit": "1024m" - } - } - }, - { - "testname": "cf_instance_guid with invalid characters", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "10.10.149.48", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "1024m", - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/pcf/error": { - "call_count": 1 - } - } - }, - { - "testname": "cf_instance_guid too long", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "10.10.149.48", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "1024m", - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/pcf/error": { - "call_count": 1 - } - } - }, - { - "testname": "cf_instance_ip with invalid characters", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "fd326c0e-847e-47a1-65cc-45f6", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "1024m", - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/pcf/error": { - "call_count": 1 - } - } - }, - { - "testname": "cf_instance_ip too long", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "fd326c0e-847e-47a1-65cc-45f6", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "1024m", - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/pcf/error": { - "call_count": 1 - } - } - }, - { - "testname": "memory_limit with invalid characters", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "fd326c0e-847e-47a1-65cc-45f6", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "10.10.149.48", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "", - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/pcf/error": { - "call_count": 1 - } - } - }, - { - "testname": "memory_limit too long", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "fd326c0e-847e-47a1-65cc-45f6", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "10.10.149.48", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/pcf/error": { - "call_count": 1 - } - } - }, - { - "testname": "UTF-8 high codepoints", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "10.10.149.48", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "1024m", - "timeout": false - } - }, - "expected_vendors_hash": { - "pcf": { - "cf_instance_guid": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "cf_instance_ip": "10.10.149.48", - "memory_limit": "1024m" - } - } - }, - { - "testname": "comma with multibyte characters", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "滈 橀槶澉 鞻饙騴 鱙鷭黂 甗糲, 紁羑 嗂 蛶觢豥 餤駰鬳 釂鱞鸄", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "10.10.149.48", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "1024m", - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/pcf/error": { - "call_count": 1 - } - } - }, - { - "testname": "Exclamation point in response", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "Bang!", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "10.10.149.48", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "1024m", - "timeout": false - } - }, - "expected_vendors_hash": null, - "expected_metrics": { - "Supportability/utilization/pcf/error": { - "call_count": 1 - } - } - }, - { - "testname": "Valid punctuation in response", - "env_vars": { - "CF_INSTANCE_GUID": { - "response": "a-b_c.3... and/or 503 867-5309", - "timeout": false - }, - "CF_INSTANCE_IP": { - "response": "10.10.149.48", - "timeout": false - }, - "MEMORY_LIMIT": { - "response": "1024m", - "timeout": false - } - }, - "expected_vendors_hash": { - "pcf": { - "cf_instance_guid": "a-b_c.3... and/or 503 867-5309", - "cf_instance_ip": "10.10.149.48", - "memory_limit": "1024m" - } - } - } -] diff --git a/internal/crossagent/crossagent.go b/internal/crossagent/crossagent.go deleted file mode 100644 index e256f6908..000000000 --- a/internal/crossagent/crossagent.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package crossagent - -import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - "runtime" -) - -var ( - crossAgentDir = func() string { - if s := os.Getenv("NEW_RELIC_CROSS_AGENT_TESTS"); s != "" { - return s - } - _, here, _, _ := runtime.Caller(0) - return filepath.Join(filepath.Dir(here), "cross_agent_tests") - }() -) - -// ReadFile reads a file from the crossagent tests directory given as with -// ioutil.ReadFile. -func ReadFile(name string) ([]byte, error) { - return ioutil.ReadFile(filepath.Join(crossAgentDir, name)) -} - -// ReadJSON takes the name of a file and parses it using JSON.Unmarshal into -// the interface given. -func ReadJSON(name string, v interface{}) error { - data, err := ReadFile(name) - if err != nil { - return err - } - return json.Unmarshal(data, v) -} - -// ReadDir reads a directory relative to crossagent tests and returns an array -// of absolute filepaths of the files in that directory. -func ReadDir(name string) ([]string, error) { - dir := filepath.Join(crossAgentDir, name) - - entries, err := ioutil.ReadDir(dir) - if err != nil { - return nil, err - } - - var files []string - for _, info := range entries { - if !info.IsDir() { - files = append(files, filepath.Join(dir, info.Name())) - } - } - return files, nil -} diff --git a/internal/custom_event.go b/internal/custom_event.go deleted file mode 100644 index 876695054..000000000 --- a/internal/custom_event.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "fmt" - "regexp" - "time" -) - -// https://newrelic.atlassian.net/wiki/display/eng/Custom+Events+in+New+Relic+Agents - -var ( - eventTypeRegexRaw = `^[a-zA-Z0-9:_ ]+$` - eventTypeRegex = regexp.MustCompile(eventTypeRegexRaw) - - errEventTypeLength = fmt.Errorf("event type exceeds length limit of %d", - attributeKeyLengthLimit) - // ErrEventTypeRegex will be returned to caller of app.RecordCustomEvent - // if the event type is not valid. - ErrEventTypeRegex = fmt.Errorf("event type must match %s", eventTypeRegexRaw) - errNumAttributes = fmt.Errorf("maximum of %d attributes exceeded", - customEventAttributeLimit) -) - -// CustomEvent is a custom event. -type CustomEvent struct { - eventType string - timestamp time.Time - truncatedParams map[string]interface{} -} - -// WriteJSON prepares JSON in the format expected by the collector. -func (e *CustomEvent) WriteJSON(buf *bytes.Buffer) { - w := jsonFieldsWriter{buf: buf} - buf.WriteByte('[') - buf.WriteByte('{') - w.stringField("type", e.eventType) - w.floatField("timestamp", timeToFloatSeconds(e.timestamp)) - buf.WriteByte('}') - - buf.WriteByte(',') - buf.WriteByte('{') - w = jsonFieldsWriter{buf: buf} - for key, val := range e.truncatedParams { - writeAttributeValueJSON(&w, key, val) - } - buf.WriteByte('}') - - buf.WriteByte(',') - buf.WriteByte('{') - buf.WriteByte('}') - buf.WriteByte(']') -} - -// MarshalJSON is used for testing. -func (e *CustomEvent) MarshalJSON() ([]byte, error) { - buf := bytes.NewBuffer(make([]byte, 0, 256)) - - e.WriteJSON(buf) - - return buf.Bytes(), nil -} - -func eventTypeValidate(eventType string) error { - if len(eventType) > attributeKeyLengthLimit { - return errEventTypeLength - } - if !eventTypeRegex.MatchString(eventType) { - return ErrEventTypeRegex - } - return nil -} - -// CreateCustomEvent creates a custom event. -func CreateCustomEvent(eventType string, params map[string]interface{}, now time.Time) (*CustomEvent, error) { - if err := eventTypeValidate(eventType); nil != err { - return nil, err - } - - if len(params) > customEventAttributeLimit { - return nil, errNumAttributes - } - - truncatedParams := make(map[string]interface{}) - for key, val := range params { - val, err := ValidateUserAttribute(key, val) - if nil != err { - return nil, err - } - truncatedParams[key] = val - } - - return &CustomEvent{ - eventType: eventType, - timestamp: now, - truncatedParams: truncatedParams, - }, nil -} - -// MergeIntoHarvest implements Harvestable. -func (e *CustomEvent) MergeIntoHarvest(h *Harvest) { - h.CustomEvents.Add(e) -} diff --git a/internal/custom_event_test.go b/internal/custom_event_test.go deleted file mode 100644 index 760ebe96e..000000000 --- a/internal/custom_event_test.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "strconv" - "testing" - "time" -) - -var ( - now = time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - strLen512 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - strLen255 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" -) - -// Tests use a single key-value pair in params to ensure deterministic JSON -// ordering. - -func TestCreateCustomEventSuccess(t *testing.T) { - event, err := CreateCustomEvent("myEvent", map[string]interface{}{"alpha": 1}, now) - if nil != err { - t.Fatal(err) - } - js, err := json.Marshal(event) - if nil != err { - t.Fatal(err) - } - if string(js) != `[{"type":"myEvent","timestamp":1.41713646e+09},{"alpha":1},{}]` { - t.Fatal(string(js)) - } -} - -func TestInvalidEventTypeCharacter(t *testing.T) { - event, err := CreateCustomEvent("myEvent!", map[string]interface{}{"alpha": 1}, now) - if err != ErrEventTypeRegex { - t.Fatal(err) - } - if nil != event { - t.Fatal(event) - } -} - -func TestLongEventType(t *testing.T) { - event, err := CreateCustomEvent(strLen512, map[string]interface{}{"alpha": 1}, now) - if err != errEventTypeLength { - t.Fatal(err) - } - if nil != event { - t.Fatal(event) - } -} - -func TestNilParams(t *testing.T) { - event, err := CreateCustomEvent("myEvent", nil, now) - if nil != err { - t.Fatal(err) - } - js, err := json.Marshal(event) - if nil != err { - t.Fatal(err) - } - if string(js) != `[{"type":"myEvent","timestamp":1.41713646e+09},{},{}]` { - t.Fatal(string(js)) - } -} - -func TestMissingEventType(t *testing.T) { - event, err := CreateCustomEvent("", map[string]interface{}{"alpha": 1}, now) - if err != ErrEventTypeRegex { - t.Fatal(err) - } - if nil != event { - t.Fatal(event) - } -} - -func TestEmptyParams(t *testing.T) { - event, err := CreateCustomEvent("myEvent", map[string]interface{}{}, now) - if nil != err { - t.Fatal(err) - } - js, err := json.Marshal(event) - if nil != err { - t.Fatal(err) - } - if string(js) != `[{"type":"myEvent","timestamp":1.41713646e+09},{},{}]` { - t.Fatal(string(js)) - } -} - -func TestTruncatedStringValue(t *testing.T) { - event, err := CreateCustomEvent("myEvent", map[string]interface{}{"alpha": strLen512}, now) - if nil != err { - t.Fatal(err) - } - js, err := json.Marshal(event) - if nil != err { - t.Fatal(err) - } - if string(js) != `[{"type":"myEvent","timestamp":1.41713646e+09},{"alpha":"`+strLen255+`"},{}]` { - t.Fatal(string(js)) - } -} - -func TestInvalidValueType(t *testing.T) { - event, err := CreateCustomEvent("myEvent", map[string]interface{}{"alpha": []string{}}, now) - if _, ok := err.(ErrInvalidAttributeType); !ok { - t.Fatal(err) - } - if nil != event { - t.Fatal(event) - } -} - -func TestInvalidCustomAttributeKey(t *testing.T) { - event, err := CreateCustomEvent("myEvent", map[string]interface{}{strLen512: 1}, now) - if nil == err { - t.Fatal(err) - } - if _, ok := err.(invalidAttributeKeyErr); !ok { - t.Fatal(err) - } - if nil != event { - t.Fatal(event) - } -} - -func TestTooManyAttributes(t *testing.T) { - params := make(map[string]interface{}) - for i := 0; i < customEventAttributeLimit+1; i++ { - params[strconv.Itoa(i)] = i - } - event, err := CreateCustomEvent("myEvent", params, now) - if errNumAttributes != err { - t.Fatal(err) - } - if nil != event { - t.Fatal(event) - } -} - -func TestCustomEventAttributeTypes(t *testing.T) { - testcases := []struct { - val interface{} - js string - }{ - {"string", `"string"`}, - {true, `true`}, - {false, `false`}, - {uint8(1), `1`}, - {uint16(1), `1`}, - {uint32(1), `1`}, - {uint64(1), `1`}, - {int8(1), `1`}, - {int16(1), `1`}, - {int32(1), `1`}, - {int64(1), `1`}, - {float32(1), `1`}, - {float64(1), `1`}, - {uint(1), `1`}, - {int(1), `1`}, - {uintptr(1), `1`}, - } - - for _, tc := range testcases { - event, err := CreateCustomEvent("myEvent", map[string]interface{}{"key": tc.val}, now) - if nil != err { - t.Fatal(err) - } - js, err := json.Marshal(event) - if nil != err { - t.Fatal(err) - } - if string(js) != `[{"type":"myEvent","timestamp":1.41713646e+09},{"key":`+tc.js+`},{}]` { - t.Fatal(string(js)) - } - } -} - -func TestCustomParamsCopied(t *testing.T) { - params := map[string]interface{}{"alpha": 1} - event, err := CreateCustomEvent("myEvent", params, now) - if nil != err { - t.Fatal(err) - } - // Attempt to change the params after the event created: - params["zip"] = "zap" - js, err := json.Marshal(event) - if nil != err { - t.Fatal(err) - } - if string(js) != `[{"type":"myEvent","timestamp":1.41713646e+09},{"alpha":1},{}]` { - t.Fatal(string(js)) - } -} - -func TestMultipleAttributeJSON(t *testing.T) { - params := map[string]interface{}{"alpha": 1, "beta": 2} - event, err := CreateCustomEvent("myEvent", params, now) - if nil != err { - t.Fatal(err) - } - js, err := json.Marshal(event) - if nil != err { - t.Fatal(err) - } - // Params order may not be deterministic, so we simply test that the - // JSON created is valid. - var valid interface{} - if err := json.Unmarshal(js, &valid); nil != err { - t.Error(string(js)) - } -} diff --git a/internal/custom_events.go b/internal/custom_events.go deleted file mode 100644 index f874f743a..000000000 --- a/internal/custom_events.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import "time" - -type customEvents struct { - *analyticsEvents -} - -func newCustomEvents(max int) *customEvents { - return &customEvents{ - analyticsEvents: newAnalyticsEvents(max), - } -} - -func (cs *customEvents) Add(e *CustomEvent) { - // For the Go Agent, customEvents are added to the application, not the transaction. - // As a result, customEvents do not inherit their priority from the transaction, though - // they are still sampled according to priority sampling. - priority := NewPriority() - cs.addEvent(analyticsEvent{priority, e}) -} - -func (cs *customEvents) MergeIntoHarvest(h *Harvest) { - h.CustomEvents.mergeFailed(cs.analyticsEvents) -} - -func (cs *customEvents) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { - return cs.CollectorJSON(agentRunID) -} - -func (cs *customEvents) EndpointMethod() string { - return cmdCustomEvents -} diff --git a/internal/custom_metric.go b/internal/custom_metric.go deleted file mode 100644 index 835805f97..000000000 --- a/internal/custom_metric.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -// CustomMetric is a custom metric. -type CustomMetric struct { - RawInputName string - Value float64 -} - -// MergeIntoHarvest implements Harvestable. -func (m CustomMetric) MergeIntoHarvest(h *Harvest) { - h.Metrics.addValue(customMetric(m.RawInputName), "", m.Value, unforced) -} diff --git a/internal/distributed_tracing.go b/internal/distributed_tracing.go deleted file mode 100644 index f1d5b364f..000000000 --- a/internal/distributed_tracing.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "time" -) - -type distTraceVersion [2]int - -func (v distTraceVersion) major() int { return v[0] } -func (v distTraceVersion) minor() int { return v[1] } - -const ( - // CallerType is the Type field's value for outbound payloads. - CallerType = "App" -) - -var ( - currentDistTraceVersion = distTraceVersion([2]int{0 /* Major */, 1 /* Minor */}) - callerUnknown = payloadCaller{Type: "Unknown", App: "Unknown", Account: "Unknown", TransportType: "Unknown"} -) - -// timestampMillis allows raw payloads to use exact times, and marshalled -// payloads to use times in millis. -type timestampMillis time.Time - -func (tm *timestampMillis) UnmarshalJSON(data []byte) error { - var millis uint64 - if err := json.Unmarshal(data, &millis); nil != err { - return err - } - *tm = timestampMillis(timeFromUnixMilliseconds(millis)) - return nil -} - -func (tm timestampMillis) MarshalJSON() ([]byte, error) { - return json.Marshal(TimeToUnixMilliseconds(tm.Time())) -} - -func (tm timestampMillis) Time() time.Time { return time.Time(tm) } -func (tm *timestampMillis) Set(t time.Time) { *tm = timestampMillis(t) } - -// Payload is the distributed tracing payload. -type Payload struct { - payloadCaller - TransactionID string `json:"tx,omitempty"` - ID string `json:"id,omitempty"` - TracedID string `json:"tr"` - Priority Priority `json:"pr"` - Sampled *bool `json:"sa"` - Timestamp timestampMillis `json:"ti"` - TransportDuration time.Duration `json:"-"` -} - -type payloadCaller struct { - TransportType string `json:"-"` - Type string `json:"ty"` - App string `json:"ap"` - Account string `json:"ac"` - TrustedAccountKey string `json:"tk,omitempty"` -} - -// IsValid validates the payload data by looking for missing fields. -// Returns an error if there's a problem, nil if everything's fine -func (p Payload) IsValid() error { - - // If a payload is missing both `guid` and `transactionId` is received, - // a ParseException supportability metric should be generated. - if "" == p.TransactionID && "" == p.ID { - return ErrPayloadMissingField{message: "missing both guid/id and TransactionId/tx"} - } - - if "" == p.Type { - return ErrPayloadMissingField{message: "missing Type/ty"} - } - - if "" == p.Account { - return ErrPayloadMissingField{message: "missing Account/ac"} - } - - if "" == p.App { - return ErrPayloadMissingField{message: "missing App/ap"} - } - - if "" == p.TracedID { - return ErrPayloadMissingField{message: "missing TracedID/tr"} - } - - if p.Timestamp.Time().IsZero() || 0 == p.Timestamp.Time().Unix() { - return ErrPayloadMissingField{message: "missing Timestamp/ti"} - } - - return nil -} - -func (p Payload) text(v distTraceVersion) []byte { - js, _ := json.Marshal(struct { - Version distTraceVersion `json:"v"` - Data Payload `json:"d"` - }{ - Version: v, - Data: p, - }) - return js -} - -// Text implements newrelic.DistributedTracePayload. -func (p Payload) Text() string { - t := p.text(currentDistTraceVersion) - return string(t) -} - -// HTTPSafe implements newrelic.DistributedTracePayload. -func (p Payload) HTTPSafe() string { - t := p.text(currentDistTraceVersion) - return base64.StdEncoding.EncodeToString(t) -} - -// SetSampled lets us set a value for our *bool, -// which we can't do directly since a pointer -// needs something to point at. -func (p *Payload) SetSampled(sampled bool) { - p.Sampled = &sampled -} - -// ErrPayloadParse indicates that the payload was malformed. -type ErrPayloadParse struct{ err error } - -func (e ErrPayloadParse) Error() string { - return fmt.Sprintf("unable to parse inbound payload: %s", e.err.Error()) -} - -// ErrPayloadMissingField indicates there's a required field that's missing -type ErrPayloadMissingField struct{ message string } - -func (e ErrPayloadMissingField) Error() string { - return fmt.Sprintf("payload is missing required fields: %s", e.message) -} - -// ErrUnsupportedPayloadVersion indicates that the major version number is -// unknown. -type ErrUnsupportedPayloadVersion struct{ version int } - -func (e ErrUnsupportedPayloadVersion) Error() string { - return fmt.Sprintf("unsupported major version number %d", e.version) -} - -// AcceptPayload parses the inbound distributed tracing payload. -func AcceptPayload(p interface{}) (*Payload, error) { - var payload Payload - if byteSlice, ok := p.([]byte); ok { - p = string(byteSlice) - } - switch v := p.(type) { - case string: - if "" == v { - return nil, nil - } - var decoded []byte - if '{' == v[0] { - decoded = []byte(v) - } else { - var err error - decoded, err = base64.StdEncoding.DecodeString(v) - if nil != err { - return nil, ErrPayloadParse{err: err} - } - } - envelope := struct { - Version distTraceVersion `json:"v"` - Data json.RawMessage `json:"d"` - }{} - if err := json.Unmarshal(decoded, &envelope); nil != err { - return nil, ErrPayloadParse{err: err} - } - - if 0 == envelope.Version.major() && 0 == envelope.Version.minor() { - return nil, ErrPayloadMissingField{message: "missing v"} - } - - if envelope.Version.major() > currentDistTraceVersion.major() { - return nil, ErrUnsupportedPayloadVersion{ - version: envelope.Version.major(), - } - } - if err := json.Unmarshal(envelope.Data, &payload); nil != err { - return nil, ErrPayloadParse{err: err} - } - case Payload: - payload = v - default: - // Could be a shim payload (if the app is not yet connected). - return nil, nil - } - // Ensure that we don't have a reference to the input payload: we don't - // want to change it, it could be used multiple times. - alloc := new(Payload) - *alloc = payload - - return alloc, nil -} diff --git a/internal/distributed_tracing_test.go b/internal/distributed_tracing_test.go deleted file mode 100644 index 956f9a3d6..000000000 --- a/internal/distributed_tracing_test.go +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "testing" - "time" -) - -var ( - samplePayload = Payload{ - payloadCaller: payloadCaller{ - Type: CallerType, - Account: "123", - App: "456", - }, - ID: "myid", - TracedID: "mytrip", - Priority: 0.12345, - Timestamp: timestampMillis(time.Now()), - } -) - -func TestPayloadRaw(t *testing.T) { - out, err := AcceptPayload(samplePayload) - if err != nil || out == nil { - t.Fatal(err, out) - } - if samplePayload != *out { - t.Fatal(samplePayload, out) - } -} - -func TestPayloadNil(t *testing.T) { - out, err := AcceptPayload(nil) - if err != nil || out != nil { - t.Fatal(err, out) - } -} - -func TestPayloadText(t *testing.T) { - out, err := AcceptPayload(samplePayload.Text()) - if err != nil || out == nil { - t.Fatal(err, out) - } - out.Timestamp = samplePayload.Timestamp // account for timezone differences - if samplePayload != *out { - t.Fatal(samplePayload, out) - } -} - -func TestPayloadTextByteSlice(t *testing.T) { - out, err := AcceptPayload([]byte(samplePayload.Text())) - if err != nil || out == nil { - t.Fatal(err, out) - } - out.Timestamp = samplePayload.Timestamp // account for timezone differences - if samplePayload != *out { - t.Fatal(samplePayload, out) - } -} - -func TestPayloadHTTPSafe(t *testing.T) { - out, err := AcceptPayload(samplePayload.HTTPSafe()) - if err != nil || nil == out { - t.Fatal(err, out) - } - out.Timestamp = samplePayload.Timestamp // account for timezone differences - if samplePayload != *out { - t.Fatal(samplePayload, out) - } -} - -func TestPayloadHTTPSafeByteSlice(t *testing.T) { - out, err := AcceptPayload([]byte(samplePayload.HTTPSafe())) - if err != nil || nil == out { - t.Fatal(err, out) - } - out.Timestamp = samplePayload.Timestamp // account for timezone differences - if samplePayload != *out { - t.Fatal(samplePayload, out) - } -} - -func TestPayloadInvalidBase64(t *testing.T) { - out, err := AcceptPayload("======") - if _, ok := err.(ErrPayloadParse); !ok { - t.Fatal(err) - } - if nil != out { - t.Fatal(out) - } -} - -func TestPayloadEmptyString(t *testing.T) { - out, err := AcceptPayload("") - if err != nil { - t.Fatal(err) - } - if nil != out { - t.Fatal(out) - } -} - -func TestPayloadUnexpectedType(t *testing.T) { - out, err := AcceptPayload(1) - if err != nil { - t.Fatal(err) - } - if nil != out { - t.Fatal(out) - } -} - -func TestPayloadBadVersion(t *testing.T) { - futuristicVersion := distTraceVersion([2]int{ - currentDistTraceVersion[0] + 1, - currentDistTraceVersion[1] + 1, - }) - out, err := AcceptPayload(samplePayload.text(futuristicVersion)) - if _, ok := err.(ErrUnsupportedPayloadVersion); !ok { - t.Fatal(err) - } - if out != nil { - t.Fatal(out) - } -} - -func TestPayloadBadEnvelope(t *testing.T) { - out, err := AcceptPayload("{") - if _, ok := err.(ErrPayloadParse); !ok { - t.Fatal(err) - } - if out != nil { - t.Fatal(out) - } -} - -func TestPayloadBadPayload(t *testing.T) { - var envelope map[string]interface{} - if err := json.Unmarshal([]byte(samplePayload.Text()), &envelope); nil != err { - t.Fatal(err) - } - envelope["d"] = "123" - payload, err := json.Marshal(envelope) - if nil != err { - t.Fatal(err) - } - out, err := AcceptPayload(payload) - if _, ok := err.(ErrPayloadParse); !ok { - t.Fatal(err) - } - if out != nil { - t.Fatal(out) - } -} - -func TestTimestampMillisMarshalUnmarshal(t *testing.T) { - var sec int64 = 111 - var millis int64 = 222 - var micros int64 = 333 - var nsecWithMicros = 1000*1000*millis + 1000*micros - var nsecWithoutMicros = 1000 * 1000 * millis - - input := time.Unix(sec, nsecWithMicros) - expectOutput := time.Unix(sec, nsecWithoutMicros) - - var tm timestampMillis - tm.Set(input) - js, err := json.Marshal(tm) - if nil != err { - t.Fatal(err) - } - var out timestampMillis - err = json.Unmarshal(js, &out) - if nil != err { - t.Fatal(err) - } - if out.Time() != expectOutput { - t.Fatal(out.Time(), expectOutput) - } -} - -func BenchmarkPayloadText(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - - for n := 0; n < b.N; n++ { - samplePayload.Text() - } -} - -func TestEmptyPayloadData(t *testing.T) { - // does an empty payload json blob result in an invalid payload - var payload Payload - fixture := []byte(`{}`) - - if err := json.Unmarshal(fixture, &payload); nil != err { - t.Log("Could not marshall fixture data into payload") - t.Error(err) - } - - if err := payload.IsValid(); err == nil { - t.Log("Expected error from empty payload data") - t.Fail() - } -} - -func TestRequiredFieldsPayloadData(t *testing.T) { - var payload Payload - fixture := []byte(`{ - "ty":"App", - "ac":"123", - "ap":"456", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - }`) - - if err := json.Unmarshal(fixture, &payload); nil != err { - t.Log("Could not marshall fixture data into payload") - t.Error(err) - } - - if err := payload.IsValid(); err != nil { - t.Log("Expected valid payload if ty, ac, ap, id, tr, and ti are set") - t.Error(err) - } -} - -func TestRequiredFieldsMissingType(t *testing.T) { - var payload Payload - fixture := []byte(`{ - "ac":"123", - "ap":"456", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - }`) - - if err := json.Unmarshal(fixture, &payload); nil != err { - t.Log("Could not marshall fixture data into payload") - t.Error(err) - } - - if err := payload.IsValid(); err == nil { - t.Log("Expected error from missing Type (ty)") - t.Fail() - } -} - -func TestRequiredFieldsMissingAccount(t *testing.T) { - var payload Payload - fixture := []byte(`{ - "ty":"App", - "ap":"456", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - }`) - - if err := json.Unmarshal(fixture, &payload); nil != err { - t.Log("Could not marshall fixture data into payload") - t.Error(err) - } - - if err := payload.IsValid(); err == nil { - t.Log("Expected error from missing Account (ac)") - t.Fail() - } -} - -func TestRequiredFieldsMissingApp(t *testing.T) { - var payload Payload - fixture := []byte(`{ - "ty":"App", - "ac":"123", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - }`) - - if err := json.Unmarshal(fixture, &payload); nil != err { - t.Log("Could not marshall fixture data into payload") - t.Error(err) - } - - if err := payload.IsValid(); err == nil { - t.Log("Expected error from missing App (ap)") - t.Fail() - } -} - -func TestRequiredFieldsMissingTimestamp(t *testing.T) { - var payload Payload - fixture := []byte(`{ - "ty":"App", - "ac":"123", - "ap":"456", - "tr":"traceID" - }`) - - if err := json.Unmarshal(fixture, &payload); nil != err { - t.Log("Could not marshall fixture data into payload") - t.Error(err) - } - - if err := payload.IsValid(); err == nil { - t.Log("Expected error from missing Timestamp (ti)") - t.Fail() - } -} - -func TestRequiredFieldsZeroTimestamp(t *testing.T) { - var payload Payload - fixture := []byte(`{ - "ty":"App", - "ac":"123", - "ap":"456", - "tr":"traceID", - "ti":0 - }`) - - if err := json.Unmarshal(fixture, &payload); nil != err { - t.Log("Could not marshall fixture data into payload") - t.Error(err) - } - - if err := payload.IsValid(); err == nil { - t.Log("Expected error from missing Timestamp (ti)") - t.Fail() - } -} diff --git a/internal/environment.go b/internal/environment.go deleted file mode 100644 index d6f56ccfa..000000000 --- a/internal/environment.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "reflect" - "runtime" -) - -// Environment describes the application's environment. -type Environment struct { - Compiler string `env:"runtime.Compiler"` - GOARCH string `env:"runtime.GOARCH"` - GOOS string `env:"runtime.GOOS"` - Version string `env:"runtime.Version"` - NumCPU int `env:"runtime.NumCPU"` -} - -var ( - // SampleEnvironment is useful for testing. - SampleEnvironment = Environment{ - Compiler: "comp", - GOARCH: "arch", - GOOS: "goos", - Version: "vers", - NumCPU: 8, - } -) - -// NewEnvironment returns a new Environment. -func NewEnvironment() Environment { - return Environment{ - Compiler: runtime.Compiler, - GOARCH: runtime.GOARCH, - GOOS: runtime.GOOS, - Version: runtime.Version(), - NumCPU: runtime.NumCPU(), - } -} - -// MarshalJSON prepares Environment JSON in the format expected by the collector -// during the connect command. -func (e Environment) MarshalJSON() ([]byte, error) { - var arr [][]interface{} - - val := reflect.ValueOf(e) - numFields := val.NumField() - - arr = make([][]interface{}, numFields) - - for i := 0; i < numFields; i++ { - v := val.Field(i) - t := val.Type().Field(i).Tag.Get("env") - - arr[i] = []interface{}{ - t, - v.Interface(), - } - } - - return json.Marshal(arr) -} diff --git a/internal/environment_test.go b/internal/environment_test.go deleted file mode 100644 index 5ca152da0..000000000 --- a/internal/environment_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "runtime" - "testing" -) - -func TestMarshalEnvironment(t *testing.T) { - js, err := json.Marshal(&SampleEnvironment) - if nil != err { - t.Fatal(err) - } - expect := CompactJSONString(`[ - ["runtime.Compiler","comp"], - ["runtime.GOARCH","arch"], - ["runtime.GOOS","goos"], - ["runtime.Version","vers"], - ["runtime.NumCPU",8]]`) - if string(js) != expect { - t.Fatal(string(js)) - } -} - -func TestEnvironmentFields(t *testing.T) { - env := NewEnvironment() - if env.Compiler != runtime.Compiler { - t.Error(env.Compiler, runtime.Compiler) - } - if env.GOARCH != runtime.GOARCH { - t.Error(env.GOARCH, runtime.GOARCH) - } - if env.GOOS != runtime.GOOS { - t.Error(env.GOOS, runtime.GOOS) - } - if env.Version != runtime.Version() { - t.Error(env.Version, runtime.Version()) - } - if env.NumCPU != runtime.NumCPU() { - t.Error(env.NumCPU, runtime.NumCPU()) - } -} diff --git a/internal/error_events.go b/internal/error_events.go deleted file mode 100644 index 9ad1efe25..000000000 --- a/internal/error_events.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "time" -) - -// MarshalJSON is used for testing. -func (e *ErrorEvent) MarshalJSON() ([]byte, error) { - buf := bytes.NewBuffer(make([]byte, 0, 256)) - - e.WriteJSON(buf) - - return buf.Bytes(), nil -} - -// WriteJSON prepares JSON in the format expected by the collector. -// https://source.datanerd.us/agents/agent-specs/blob/master/Error-Events.md -func (e *ErrorEvent) WriteJSON(buf *bytes.Buffer) { - w := jsonFieldsWriter{buf: buf} - buf.WriteByte('[') - buf.WriteByte('{') - w.stringField("type", "TransactionError") - w.stringField("error.class", e.Klass) - w.stringField("error.message", e.Msg) - w.floatField("timestamp", timeToFloatSeconds(e.When)) - w.stringField("transactionName", e.FinalName) - - sharedTransactionIntrinsics(&e.TxnEvent, &w) - sharedBetterCATIntrinsics(&e.TxnEvent, &w) - - buf.WriteByte('}') - buf.WriteByte(',') - userAttributesJSON(e.Attrs, buf, destError, e.ErrorData.ExtraAttributes) - buf.WriteByte(',') - agentAttributesJSON(e.Attrs, buf, destError) - buf.WriteByte(']') -} - -type errorEvents struct { - *analyticsEvents -} - -func newErrorEvents(max int) *errorEvents { - return &errorEvents{ - analyticsEvents: newAnalyticsEvents(max), - } -} - -func (events *errorEvents) Add(e *ErrorEvent, priority Priority) { - events.addEvent(analyticsEvent{priority, e}) -} - -func (events *errorEvents) MergeIntoHarvest(h *Harvest) { - h.ErrorEvents.mergeFailed(events.analyticsEvents) -} - -func (events *errorEvents) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { - return events.CollectorJSON(agentRunID) -} - -func (events *errorEvents) EndpointMethod() string { - return cmdErrorEvents -} diff --git a/internal/error_events_test.go b/internal/error_events_test.go deleted file mode 100644 index 5315a65eb..000000000 --- a/internal/error_events_test.go +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "testing" - "time" -) - -func testErrorEventJSON(t testing.TB, e *ErrorEvent, expect string) { - js, err := json.Marshal(e) - if nil != err { - t.Error(err) - return - } - expect = CompactJSONString(expect) - // Type assertion to support early Go versions. - if h, ok := t.(interface { - Helper() - }); ok { - h.Helper() - } - actual := string(js) - if expect != actual { - t.Errorf("\nexpect=%s\nactual=%s\n", expect, actual) - } -} - -var ( - sampleErrorData = ErrorData{ - Klass: "*errors.errorString", - Msg: "hello", - When: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), - } -) - -func TestErrorEventMarshal(t *testing.T) { - testErrorEventJSON(t, &ErrorEvent{ - ErrorData: sampleErrorData, - TxnEvent: TxnEvent{ - FinalName: "myName", - Duration: 3 * time.Second, - Attrs: nil, - BetterCAT: BetterCAT{ - Enabled: true, - Priority: 0.5, - ID: "txn-guid-id", - }, - }, - }, `[ - { - "type":"TransactionError", - "error.class":"*errors.errorString", - "error.message":"hello", - "timestamp":1.41713646e+09, - "transactionName":"myName", - "duration":3, - "guid":"txn-guid-id", - "traceId":"txn-guid-id", - "priority":0.500000, - "sampled":false - }, - {}, - {} - ]`) - - // Many error event intrinsics are shared with txn events using sharedEventIntrinsics: See - // the txn event tests. -} - -func TestErrorEventMarshalOldCAT(t *testing.T) { - testErrorEventJSON(t, &ErrorEvent{ - ErrorData: sampleErrorData, - TxnEvent: TxnEvent{ - FinalName: "myName", - Duration: 3 * time.Second, - Attrs: nil, - BetterCAT: BetterCAT{ - Enabled: false, - }, - }, - }, `[ - { - "type":"TransactionError", - "error.class":"*errors.errorString", - "error.message":"hello", - "timestamp":1.41713646e+09, - "transactionName":"myName", - "duration":3 - }, - {}, - {} - ]`) - - // Many error event intrinsics are shared with txn events using sharedEventIntrinsics: See - // the txn event tests. -} - -func TestErrorEventAttributes(t *testing.T) { - aci := sampleAttributeConfigInput - aci.ErrorCollector.Exclude = append(aci.ErrorCollector.Exclude, "zap") - aci.ErrorCollector.Exclude = append(aci.ErrorCollector.Exclude, AttributeHostDisplayName.name()) - cfg := CreateAttributeConfig(aci, true) - attr := NewAttributes(cfg) - attr.Agent.Add(AttributeHostDisplayName, "exclude me", nil) - attr.Agent.Add(attributeRequestMethod, "GET", nil) - AddUserAttribute(attr, "zap", 123, DestAll) - AddUserAttribute(attr, "zip", 456, DestAll) - - testErrorEventJSON(t, &ErrorEvent{ - ErrorData: sampleErrorData, - TxnEvent: TxnEvent{ - FinalName: "myName", - Duration: 3 * time.Second, - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - Priority: 0.5, - ID: "txn-guid-id", - }, - }, - }, `[ - { - "type":"TransactionError", - "error.class":"*errors.errorString", - "error.message":"hello", - "timestamp":1.41713646e+09, - "transactionName":"myName", - "duration":3, - "guid":"txn-guid-id", - "traceId":"txn-guid-id", - "priority":0.500000, - "sampled":false - }, - { - "zip":456 - }, - { - "request.method":"GET" - } - ]`) -} - -func TestErrorEventAttributesOldCAT(t *testing.T) { - aci := sampleAttributeConfigInput - aci.ErrorCollector.Exclude = append(aci.ErrorCollector.Exclude, "zap") - aci.ErrorCollector.Exclude = append(aci.ErrorCollector.Exclude, AttributeHostDisplayName.name()) - cfg := CreateAttributeConfig(aci, true) - attr := NewAttributes(cfg) - attr.Agent.Add(AttributeHostDisplayName, "exclude me", nil) - attr.Agent.Add(attributeRequestMethod, "GET", nil) - AddUserAttribute(attr, "zap", 123, DestAll) - AddUserAttribute(attr, "zip", 456, DestAll) - - testErrorEventJSON(t, &ErrorEvent{ - ErrorData: sampleErrorData, - TxnEvent: TxnEvent{ - FinalName: "myName", - Duration: 3 * time.Second, - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: false, - }, - }, - }, `[ - { - "type":"TransactionError", - "error.class":"*errors.errorString", - "error.message":"hello", - "timestamp":1.41713646e+09, - "transactionName":"myName", - "duration":3 - }, - { - "zip":456 - }, - { - "request.method":"GET" - } - ]`) -} - -func TestErrorEventMarshalWithInboundCaller(t *testing.T) { - e := TxnEvent{ - FinalName: "myName", - Duration: 3 * time.Second, - Attrs: nil, - } - - e.BetterCAT.Enabled = true - e.BetterCAT.Inbound = &Payload{ - payloadCaller: payloadCaller{ - TransportType: "HTTP", - Type: "Browser", - App: "caller-app", - Account: "caller-account", - }, - ID: "caller-id", - TransactionID: "caller-parent-id", - TracedID: "trip-id", - TransportDuration: 2 * time.Second, - } - - testErrorEventJSON(t, &ErrorEvent{ - ErrorData: sampleErrorData, - TxnEvent: e, - }, `[ - { - "type":"TransactionError", - "error.class":"*errors.errorString", - "error.message":"hello", - "timestamp":1.41713646e+09, - "transactionName":"myName", - "duration":3, - "parent.type": "Browser", - "parent.app": "caller-app", - "parent.account": "caller-account", - "parent.transportType": "HTTP", - "parent.transportDuration": 2, - "guid":"", - "traceId":"trip-id", - "priority":0.000000, - "sampled":false - }, - {}, - {} - ]`) -} diff --git a/internal/errors.go b/internal/errors.go deleted file mode 100644 index cbee8fa9d..000000000 --- a/internal/errors.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/newrelic/go-agent/internal/jsonx" -) - -const ( - // PanicErrorKlass is the error klass used for errors generated by - // recovering panics in txn.End. - PanicErrorKlass = "panic" -) - -func panicValueMsg(v interface{}) string { - switch val := v.(type) { - case error: - return val.Error() - default: - return fmt.Sprintf("%v", v) - } -} - -// TxnErrorFromPanic creates a new TxnError from a panic. -func TxnErrorFromPanic(now time.Time, v interface{}) ErrorData { - return ErrorData{ - When: now, - Msg: panicValueMsg(v), - Klass: PanicErrorKlass, - } -} - -// TxnErrorFromResponseCode creates a new TxnError from an http response code. -func TxnErrorFromResponseCode(now time.Time, code int) ErrorData { - codeStr := strconv.Itoa(code) - msg := http.StatusText(code) - if msg == "" { - // Use a generic message if the code was not an http code - // to support gRPC. - msg = "response code " + codeStr - } - return ErrorData{ - When: now, - Msg: msg, - Klass: codeStr, - } -} - -// ErrorData contains the information about a recorded error. -type ErrorData struct { - When time.Time - Stack StackTrace - ExtraAttributes map[string]interface{} - Msg string - Klass string -} - -// TxnError combines error data with information about a transaction. TxnError is used for -// both error events and traced errors. -type TxnError struct { - ErrorData - TxnEvent -} - -// ErrorEvent and tracedError are separate types so that error events and traced errors can have -// different WriteJSON methods. -type ErrorEvent TxnError - -type tracedError TxnError - -// TxnErrors is a set of errors captured in a Transaction. -type TxnErrors []*ErrorData - -// NewTxnErrors returns a new empty TxnErrors. -func NewTxnErrors(max int) TxnErrors { - return make([]*ErrorData, 0, max) -} - -// Add adds a TxnError. -func (errors *TxnErrors) Add(e ErrorData) { - if len(*errors) < cap(*errors) { - *errors = append(*errors, &e) - } -} - -func (h *tracedError) WriteJSON(buf *bytes.Buffer) { - buf.WriteByte('[') - jsonx.AppendFloat(buf, timeToFloatMilliseconds(h.When)) - buf.WriteByte(',') - jsonx.AppendString(buf, h.FinalName) - buf.WriteByte(',') - jsonx.AppendString(buf, h.Msg) - buf.WriteByte(',') - jsonx.AppendString(buf, h.Klass) - buf.WriteByte(',') - - buf.WriteByte('{') - buf.WriteString(`"agentAttributes"`) - buf.WriteByte(':') - agentAttributesJSON(h.Attrs, buf, destError) - buf.WriteByte(',') - buf.WriteString(`"userAttributes"`) - buf.WriteByte(':') - userAttributesJSON(h.Attrs, buf, destError, h.ErrorData.ExtraAttributes) - buf.WriteByte(',') - buf.WriteString(`"intrinsics"`) - buf.WriteByte(':') - intrinsicsJSON(&h.TxnEvent, buf) - if nil != h.Stack { - buf.WriteByte(',') - buf.WriteString(`"stack_trace"`) - buf.WriteByte(':') - h.Stack.WriteJSON(buf) - } - buf.WriteByte('}') - - buf.WriteByte(']') -} - -// MarshalJSON is used for testing. -func (h *tracedError) MarshalJSON() ([]byte, error) { - buf := &bytes.Buffer{} - h.WriteJSON(buf) - return buf.Bytes(), nil -} - -type harvestErrors []*tracedError - -func newHarvestErrors(max int) harvestErrors { - return make([]*tracedError, 0, max) -} - -// MergeTxnErrors merges a transaction's errors into the harvest's errors. -func MergeTxnErrors(errors *harvestErrors, errs TxnErrors, txnEvent TxnEvent) { - for _, e := range errs { - if len(*errors) == cap(*errors) { - return - } - *errors = append(*errors, &tracedError{ - TxnEvent: txnEvent, - ErrorData: *e, - }) - } -} - -func (errors harvestErrors) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { - if 0 == len(errors) { - return nil, nil - } - estimate := 1024 * len(errors) - buf := bytes.NewBuffer(make([]byte, 0, estimate)) - buf.WriteByte('[') - jsonx.AppendString(buf, agentRunID) - buf.WriteByte(',') - buf.WriteByte('[') - for i, e := range errors { - if i > 0 { - buf.WriteByte(',') - } - e.WriteJSON(buf) - } - buf.WriteByte(']') - buf.WriteByte(']') - return buf.Bytes(), nil -} - -func (errors harvestErrors) MergeIntoHarvest(h *Harvest) {} - -func (errors harvestErrors) EndpointMethod() string { - return cmdErrorData -} diff --git a/internal/errors_test.go b/internal/errors_test.go deleted file mode 100644 index 2009afddd..000000000 --- a/internal/errors_test.go +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "errors" - "testing" - "time" -) - -var ( - emptyStackTrace = make([]uintptr, 0) -) - -func testExpectedJSON(t testing.TB, expect string, actual string) { - // Type assertion to support early Go versions. - if h, ok := t.(interface { - Helper() - }); ok { - h.Helper() - } - compactExpect := CompactJSONString(expect) - if compactExpect != actual { - t.Errorf("\nexpect=%s\nactual=%s\n", compactExpect, actual) - } -} - -func TestErrorTraceMarshal(t *testing.T) { - he := &tracedError{ - ErrorData: ErrorData{ - When: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), - Stack: emptyStackTrace, - Msg: "my_msg", - Klass: "my_class", - }, - TxnEvent: TxnEvent{ - FinalName: "my_txn_name", - Attrs: nil, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - TotalTime: 2 * time.Second, - }, - } - js, err := json.Marshal(he) - if nil != err { - t.Error(err) - } - - expect := ` - [ - 1.41713646e+12, - "my_txn_name", - "my_msg", - "my_class", - { - "agentAttributes":{}, - "userAttributes":{}, - "intrinsics":{ - "totalTime":2, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - }, - "stack_trace":[] - } - ]` - testExpectedJSON(t, expect, string(js)) -} - -func TestErrorTraceMarshalOldCAT(t *testing.T) { - he := &tracedError{ - ErrorData: ErrorData{ - When: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), - Stack: emptyStackTrace, - Msg: "my_msg", - Klass: "my_class", - }, - TxnEvent: TxnEvent{ - FinalName: "my_txn_name", - Attrs: nil, - BetterCAT: BetterCAT{ - Enabled: false, - }, - TotalTime: 2 * time.Second, - }, - } - js, err := json.Marshal(he) - if nil != err { - t.Error(err) - } - - expect := ` - [ - 1.41713646e+12, - "my_txn_name", - "my_msg", - "my_class", - { - "agentAttributes":{}, - "userAttributes":{}, - "intrinsics":{ - "totalTime":2 - }, - "stack_trace":[] - } - ]` - testExpectedJSON(t, expect, string(js)) -} - -func TestErrorTraceAttributes(t *testing.T) { - aci := sampleAttributeConfigInput - aci.ErrorCollector.Exclude = append(aci.ErrorCollector.Exclude, "zap") - aci.ErrorCollector.Exclude = append(aci.ErrorCollector.Exclude, AttributeHostDisplayName.name()) - cfg := CreateAttributeConfig(aci, true) - attr := NewAttributes(cfg) - attr.Agent.Add(AttributeHostDisplayName, "exclude me", nil) - attr.Agent.Add(attributeRequestURI, "my_request_uri", nil) - AddUserAttribute(attr, "zap", 123, DestAll) - AddUserAttribute(attr, "zip", 456, DestAll) - - he := &tracedError{ - ErrorData: ErrorData{ - When: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), - Stack: nil, - Msg: "my_msg", - Klass: "my_class", - }, - TxnEvent: TxnEvent{ - FinalName: "my_txn_name", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - TotalTime: 2 * time.Second, - }, - } - js, err := json.Marshal(he) - if nil != err { - t.Error(err) - } - expect := ` - [ - 1.41713646e+12, - "my_txn_name", - "my_msg", - "my_class", - { - "agentAttributes":{"request.uri":"my_request_uri"}, - "userAttributes":{"zip":456}, - "intrinsics":{ - "totalTime":2, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - } - } - ]` - testExpectedJSON(t, expect, string(js)) -} - -func TestErrorTraceAttributesOldCAT(t *testing.T) { - aci := sampleAttributeConfigInput - aci.ErrorCollector.Exclude = append(aci.ErrorCollector.Exclude, "zap") - aci.ErrorCollector.Exclude = append(aci.ErrorCollector.Exclude, AttributeHostDisplayName.name()) - cfg := CreateAttributeConfig(aci, true) - attr := NewAttributes(cfg) - attr.Agent.Add(AttributeHostDisplayName, "exclude me", nil) - attr.Agent.Add(attributeRequestURI, "my_request_uri", nil) - AddUserAttribute(attr, "zap", 123, DestAll) - AddUserAttribute(attr, "zip", 456, DestAll) - - he := &tracedError{ - ErrorData: ErrorData{ - When: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), - Stack: nil, - Msg: "my_msg", - Klass: "my_class", - }, - TxnEvent: TxnEvent{ - FinalName: "my_txn_name", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: false, - }, - TotalTime: 2 * time.Second, - }, - } - js, err := json.Marshal(he) - if nil != err { - t.Error(err) - } - expect := ` - [ - 1.41713646e+12, - "my_txn_name", - "my_msg", - "my_class", - { - "agentAttributes":{"request.uri":"my_request_uri"}, - "userAttributes":{"zip":456}, - "intrinsics":{ - "totalTime":2 - } - } - ]` - testExpectedJSON(t, expect, string(js)) -} - -func TestErrorsLifecycle(t *testing.T) { - ers := NewTxnErrors(5) - - when := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - ers.Add(TxnErrorFromResponseCode(when, 15)) - ers.Add(TxnErrorFromResponseCode(when, 400)) - ers.Add(TxnErrorFromPanic(when, errors.New("oh no panic"))) - ers.Add(TxnErrorFromPanic(when, 123)) - ers.Add(TxnErrorFromPanic(when, 123)) - - he := newHarvestErrors(4) - MergeTxnErrors(&he, ers, TxnEvent{ - FinalName: "txnName", - Attrs: nil, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - TotalTime: 2 * time.Second, - }) - js, err := he.Data("agentRunID", time.Now()) - if nil != err { - t.Error(err) - } - expect := CompactJSONString(` -[ - "agentRunID", - [ - [ - 1.41713646e+12, - "txnName", - "response code 15", - "15", - { - "agentAttributes":{}, - "userAttributes":{}, - "intrinsics":{ - "totalTime":2, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - } - } - ], - [ - 1.41713646e+12, - "txnName", - "Bad Request", - "400", - { - "agentAttributes":{}, - "userAttributes":{}, - "intrinsics":{ - "totalTime":2, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - } - } - ], - [ - 1.41713646e+12, - "txnName", - "oh no panic", - "panic", - { - "agentAttributes":{}, - "userAttributes":{}, - "intrinsics":{ - "totalTime":2, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - } - } - ], - [ - 1.41713646e+12, - "txnName", - "123", - "panic", - { - "agentAttributes":{}, - "userAttributes":{}, - "intrinsics":{ - "totalTime":2, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - } - } - ] - ] -]`) - if string(js) != expect { - t.Error(string(js), expect) - } -} - -func BenchmarkErrorsJSON(b *testing.B) { - when := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - max := 20 - ers := NewTxnErrors(max) - - for i := 0; i < max; i++ { - ers.Add(ErrorData{ - When: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), - Msg: "error message", - Klass: "error class", - }) - } - - cfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(cfg) - attr.Agent.Add(attributeRequestMethod, "GET", nil) - AddUserAttribute(attr, "zip", 456, DestAll) - - he := newHarvestErrors(max) - MergeTxnErrors(&he, ers, TxnEvent{ - FinalName: "WebTransaction/Go/hello", - Attrs: attr, - }) - - b.ReportAllocs() - b.ResetTimer() - - for n := 0; n < b.N; n++ { - js, err := he.Data("agentRundID", when) - if nil != err || nil == js { - b.Fatal(err, js) - } - } -} diff --git a/internal/expect.go b/internal/expect.go deleted file mode 100644 index d97016fc4..000000000 --- a/internal/expect.go +++ /dev/null @@ -1,598 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "fmt" - "runtime" - - "time" -) - -var ( - // Unfortunately, the resolution of time.Now() on Windows is coarse: Two - // sequential calls to time.Now() may return the same value, and tests - // which expect non-zero durations may fail. To avoid adding sleep - // statements or mocking time.Now(), those tests are skipped on Windows. - doDurationTests = runtime.GOOS != `windows` -) - -// Validator is used for testing. -type Validator interface { - Error(...interface{}) -} - -func validateStringField(v Validator, fieldName, expect, actual string) { - // If an expected value is not set, we assume the user does not want to validate it - if expect == "" { - return - } - if expect != actual { - v.Error(fieldName, "incorrect: Expected:", expect, " Got:", actual) - } -} - -type addValidatorField struct { - field interface{} - original Validator -} - -func (a addValidatorField) Error(fields ...interface{}) { - fields = append([]interface{}{a.field}, fields...) - a.original.Error(fields...) -} - -// ExtendValidator is used to add more context to a validator. -func ExtendValidator(v Validator, field interface{}) Validator { - return addValidatorField{ - field: field, - original: v, - } -} - -// WantMetric is a metric expectation. If Data is nil, then any data values are -// acceptable. If Data has len 1, then only the metric count is validated. -type WantMetric struct { - Name string - Scope string - Forced interface{} // true, false, or nil - Data []float64 -} - -// WantError is a traced error expectation. -type WantError struct { - TxnName string - Msg string - Klass string - UserAttributes map[string]interface{} - AgentAttributes map[string]interface{} -} - -func uniquePointer() *struct{} { - s := struct{}{} - return &s -} - -var ( - // MatchAnything is for use when matching attributes. - MatchAnything = uniquePointer() -) - -// WantEvent is a transaction or error event expectation. -type WantEvent struct { - Intrinsics map[string]interface{} - UserAttributes map[string]interface{} - AgentAttributes map[string]interface{} -} - -// WantTxnTrace is a transaction trace expectation. -type WantTxnTrace struct { - MetricName string - NumSegments int - UserAttributes map[string]interface{} - AgentAttributes map[string]interface{} - Intrinsics map[string]interface{} - // If the Root's SegmentName is populated then the segments will be - // tested, otherwise NumSegments will be tested. - Root WantTraceSegment -} - -// WantTraceSegment is a transaction trace segment expectation. -type WantTraceSegment struct { - SegmentName string - // RelativeStartMillis and RelativeStopMillis will be tested if they are - // provided: This makes it easy for top level tests which cannot - // control duration. - RelativeStartMillis interface{} - RelativeStopMillis interface{} - Attributes map[string]interface{} - Children []WantTraceSegment -} - -// WantSlowQuery is a slowQuery expectation. -type WantSlowQuery struct { - Count int32 - MetricName string - Query string - TxnName string - TxnURL string - DatabaseName string - Host string - PortPathOrID string - Params map[string]interface{} -} - -// HarvestTestinger is implemented by the app. It sets an empty test harvest -// and modifies the connect reply if a callback is provided. -type HarvestTestinger interface { - HarvestTesting(replyfn func(*ConnectReply)) -} - -// HarvestTesting allows integration packages to test instrumentation. -func HarvestTesting(app interface{}, replyfn func(*ConnectReply)) { - ta, ok := app.(HarvestTestinger) - if !ok { - panic("HarvestTesting type assertion failure") - } - ta.HarvestTesting(replyfn) -} - -// WantTxn provides the expectation parameters to ExpectTxnMetrics. -type WantTxn struct { - Name string - IsWeb bool - NumErrors int -} - -// ExpectTxnMetrics tests that the app contains metrics for a transaction. -func ExpectTxnMetrics(t Validator, mt *metricTable, want WantTxn) { - var metrics []WantMetric - var scope string - var allWebOther string - if want.IsWeb { - scope = "WebTransaction/Go/" + want.Name - allWebOther = "allWeb" - metrics = []WantMetric{ - {Name: "WebTransaction/Go/" + want.Name, Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/" + want.Name, Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/" + want.Name, Scope: "", Forced: false, Data: nil}, - } - } else { - scope = "OtherTransaction/Go/" + want.Name - allWebOther = "allOther" - metrics = []WantMetric{ - {Name: "OtherTransaction/Go/" + want.Name, Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/" + want.Name, Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - } - } - if want.NumErrors > 0 { - data := []float64{float64(want.NumErrors), 0, 0, 0, 0, 0} - metrics = append(metrics, []WantMetric{ - {Name: "Errors/all", Scope: "", Forced: true, Data: data}, - {Name: "Errors/" + allWebOther, Scope: "", Forced: true, Data: data}, - {Name: "Errors/" + scope, Scope: "", Forced: true, Data: data}, - }...) - } - ExpectMetrics(t, mt, metrics) -} - -// Expect exposes methods that allow for testing whether the correct data was -// captured. -type Expect interface { - ExpectCustomEvents(t Validator, want []WantEvent) - ExpectErrors(t Validator, want []WantError) - ExpectErrorEvents(t Validator, want []WantEvent) - - ExpectTxnEvents(t Validator, want []WantEvent) - - ExpectMetrics(t Validator, want []WantMetric) - ExpectMetricsPresent(t Validator, want []WantMetric) - ExpectTxnMetrics(t Validator, want WantTxn) - - ExpectTxnTraces(t Validator, want []WantTxnTrace) - ExpectSlowQueries(t Validator, want []WantSlowQuery) - - ExpectSpanEvents(t Validator, want []WantEvent) -} - -func expectMetricField(t Validator, id metricID, v1, v2 float64, fieldName string) { - if v1 != v2 { - t.Error("metric fields do not match", id, v1, v2, fieldName) - } -} - -// ExpectMetricsPresent allows testing of metrics without requiring an exact match -func ExpectMetricsPresent(t Validator, mt *metricTable, expect []WantMetric) { - expectMetrics(t, mt, expect, false) -} - -// ExpectMetrics allows testing of metrics. It passes if mt exactly matches expect. -func ExpectMetrics(t Validator, mt *metricTable, expect []WantMetric) { - expectMetrics(t, mt, expect, true) -} - -func expectMetrics(t Validator, mt *metricTable, expect []WantMetric, exactMatch bool) { - if exactMatch { - if len(mt.metrics) != len(expect) { - t.Error("metric counts do not match expectations", len(mt.metrics), len(expect)) - } - } - expectedIds := make(map[metricID]struct{}) - for _, e := range expect { - id := metricID{Name: e.Name, Scope: e.Scope} - expectedIds[id] = struct{}{} - m := mt.metrics[id] - if nil == m { - t.Error("unable to find metric", id) - continue - } - - if b, ok := e.Forced.(bool); ok { - if b != (forced == m.forced) { - t.Error("metric forced incorrect", b, m.forced, id) - } - } - - if nil != e.Data { - expectMetricField(t, id, e.Data[0], m.data.countSatisfied, "countSatisfied") - - if len(e.Data) > 1 { - expectMetricField(t, id, e.Data[1], m.data.totalTolerated, "totalTolerated") - expectMetricField(t, id, e.Data[2], m.data.exclusiveFailed, "exclusiveFailed") - expectMetricField(t, id, e.Data[3], m.data.min, "min") - expectMetricField(t, id, e.Data[4], m.data.max, "max") - expectMetricField(t, id, e.Data[5], m.data.sumSquares, "sumSquares") - } - } - } - if exactMatch { - for id := range mt.metrics { - if _, ok := expectedIds[id]; !ok { - t.Error("expected metrics does not contain", id.Name, id.Scope) - } - } - } -} - -func expectAttributes(v Validator, exists map[string]interface{}, expect map[string]interface{}) { - // TODO: This params comparison can be made smarter: Alert differences - // based on sub/super set behavior. - if len(exists) != len(expect) { - v.Error("attributes length difference", len(exists), len(expect)) - } - for key, val := range expect { - found, ok := exists[key] - if !ok { - v.Error("expected attribute not found: ", key) - continue - } - if val == MatchAnything { - continue - } - v1 := fmt.Sprint(found) - v2 := fmt.Sprint(val) - if v1 != v2 { - v.Error("value difference", fmt.Sprintf("key=%s", key), v1, v2) - } - } - for key, val := range exists { - _, ok := expect[key] - if !ok { - v.Error("unexpected attribute present: ", key, val) - continue - } - } -} - -// ExpectCustomEvents allows testing of custom events. It passes if cs exactly matches expect. -func ExpectCustomEvents(v Validator, cs *customEvents, expect []WantEvent) { - expectEvents(v, cs.analyticsEvents, expect, nil) -} - -func expectEvent(v Validator, e json.Marshaler, expect WantEvent) { - js, err := e.MarshalJSON() - if nil != err { - v.Error("unable to marshal event", err) - return - } - var event []map[string]interface{} - err = json.Unmarshal(js, &event) - if nil != err { - v.Error("unable to parse event json", err) - return - } - intrinsics := event[0] - userAttributes := event[1] - agentAttributes := event[2] - - if nil != expect.Intrinsics { - expectAttributes(v, intrinsics, expect.Intrinsics) - } - if nil != expect.UserAttributes { - expectAttributes(v, userAttributes, expect.UserAttributes) - } - if nil != expect.AgentAttributes { - expectAttributes(v, agentAttributes, expect.AgentAttributes) - } -} - -func expectEvents(v Validator, events *analyticsEvents, expect []WantEvent, extraAttributes map[string]interface{}) { - if len(events.events) != len(expect) { - v.Error("number of events does not match", len(events.events), len(expect)) - return - } - for i, e := range expect { - event, ok := events.events[i].jsonWriter.(json.Marshaler) - if !ok { - v.Error("event does not implement json.Marshaler") - continue - } - if nil != e.Intrinsics { - e.Intrinsics = mergeAttributes(extraAttributes, e.Intrinsics) - } - expectEvent(v, event, e) - } -} - -// Second attributes have priority. -func mergeAttributes(a1, a2 map[string]interface{}) map[string]interface{} { - a := make(map[string]interface{}) - for k, v := range a1 { - a[k] = v - } - for k, v := range a2 { - a[k] = v - } - return a -} - -// ExpectErrorEvents allows testing of error events. It passes if events exactly matches expect. -func ExpectErrorEvents(v Validator, events *errorEvents, expect []WantEvent) { - expectEvents(v, events.analyticsEvents, expect, map[string]interface{}{ - // The following intrinsics should always be present in - // error events: - "type": "TransactionError", - "timestamp": MatchAnything, - "duration": MatchAnything, - }) -} - -// ExpectSpanEvents allows testing of span events. It passes if events exactly matches expect. -func ExpectSpanEvents(v Validator, events *spanEvents, expect []WantEvent) { - expectEvents(v, events.analyticsEvents, expect, map[string]interface{}{ - // The following intrinsics should always be present in - // span events: - "type": "Span", - "timestamp": MatchAnything, - "duration": MatchAnything, - "traceId": MatchAnything, - "guid": MatchAnything, - "transactionId": MatchAnything, - // All span events are currently sampled. - "sampled": true, - "priority": MatchAnything, - }) -} - -// ExpectTxnEvents allows testing of txn events. -func ExpectTxnEvents(v Validator, events *txnEvents, expect []WantEvent) { - expectEvents(v, events.analyticsEvents, expect, map[string]interface{}{ - // The following intrinsics should always be present in - // txn events: - "type": "Transaction", - "timestamp": MatchAnything, - "duration": MatchAnything, - "totalTime": MatchAnything, - "error": MatchAnything, - }) -} - -func expectError(v Validator, err *tracedError, expect WantError) { - validateStringField(v, "txnName", expect.TxnName, err.FinalName) - validateStringField(v, "klass", expect.Klass, err.Klass) - validateStringField(v, "msg", expect.Msg, err.Msg) - js, errr := err.MarshalJSON() - if nil != errr { - v.Error("unable to marshal error json", errr) - return - } - var unmarshalled []interface{} - errr = json.Unmarshal(js, &unmarshalled) - if nil != errr { - v.Error("unable to unmarshal error json", errr) - return - } - attributes := unmarshalled[4].(map[string]interface{}) - agentAttributes := attributes["agentAttributes"].(map[string]interface{}) - userAttributes := attributes["userAttributes"].(map[string]interface{}) - - if nil != expect.UserAttributes { - expectAttributes(v, userAttributes, expect.UserAttributes) - } - if nil != expect.AgentAttributes { - expectAttributes(v, agentAttributes, expect.AgentAttributes) - } - if stack := attributes["stack_trace"]; nil == stack { - v.Error("missing error stack trace") - } -} - -// ExpectErrors allows testing of errors. -func ExpectErrors(v Validator, errors harvestErrors, expect []WantError) { - if len(errors) != len(expect) { - v.Error("number of errors mismatch", len(errors), len(expect)) - return - } - for i, e := range expect { - expectError(v, errors[i], e) - } -} - -func countSegments(node []interface{}) int { - count := 1 - children := node[4].([]interface{}) - for _, c := range children { - node := c.([]interface{}) - count += countSegments(node) - } - return count -} - -func expectTraceSegment(v Validator, nodeObj interface{}, expect WantTraceSegment) { - node := nodeObj.([]interface{}) - start := int(node[0].(float64)) - stop := int(node[1].(float64)) - name := node[2].(string) - attributes := node[3].(map[string]interface{}) - children := node[4].([]interface{}) - - validateStringField(v, "segmentName", expect.SegmentName, name) - if nil != expect.RelativeStartMillis { - expectStart, ok := expect.RelativeStartMillis.(int) - if !ok { - v.Error("invalid expect.RelativeStartMillis", expect.RelativeStartMillis) - } else if expectStart != start { - v.Error("segmentStartTime", expect.SegmentName, start, expectStart) - } - } - if nil != expect.RelativeStopMillis { - expectStop, ok := expect.RelativeStopMillis.(int) - if !ok { - v.Error("invalid expect.RelativeStopMillis", expect.RelativeStopMillis) - } else if expectStop != stop { - v.Error("segmentStopTime", expect.SegmentName, stop, expectStop) - } - } - if nil != expect.Attributes { - expectAttributes(v, attributes, expect.Attributes) - } - if len(children) != len(expect.Children) { - v.Error("segmentChildrenCount", expect.SegmentName, len(children), len(expect.Children)) - } else { - for idx, child := range children { - expectTraceSegment(v, child, expect.Children[idx]) - } - } -} - -func expectTxnTrace(v Validator, got interface{}, expect WantTxnTrace) { - unmarshalled := got.([]interface{}) - duration := unmarshalled[1].(float64) - name := unmarshalled[2].(string) - var arrayURL string - if nil != unmarshalled[3] { - arrayURL = unmarshalled[3].(string) - } - traceData := unmarshalled[4].([]interface{}) - - rootNode := traceData[3].([]interface{}) - attributes := traceData[4].(map[string]interface{}) - userAttributes := attributes["userAttributes"].(map[string]interface{}) - agentAttributes := attributes["agentAttributes"].(map[string]interface{}) - intrinsics := attributes["intrinsics"].(map[string]interface{}) - - validateStringField(v, "metric name", expect.MetricName, name) - - if doDurationTests && 0 == duration { - v.Error("zero trace duration") - } - - if nil != expect.UserAttributes { - expectAttributes(v, userAttributes, expect.UserAttributes) - } - if nil != expect.AgentAttributes { - expectAttributes(v, agentAttributes, expect.AgentAttributes) - expectURL, _ := expect.AgentAttributes["request.uri"].(string) - if "" != expectURL { - validateStringField(v, "request url in array", expectURL, arrayURL) - } - } - if nil != expect.Intrinsics { - expectAttributes(v, intrinsics, expect.Intrinsics) - } - if expect.Root.SegmentName != "" { - expectTraceSegment(v, rootNode, expect.Root) - } else { - numSegments := countSegments(rootNode) - // The expectation segment count does not include the two root nodes. - numSegments -= 2 - if expect.NumSegments != numSegments { - v.Error("wrong number of segments", expect.NumSegments, numSegments) - } - } -} - -// ExpectTxnTraces allows testing of transaction traces. -func ExpectTxnTraces(v Validator, traces *harvestTraces, want []WantTxnTrace) { - if len(want) != traces.Len() { - v.Error("number of traces do not match", len(want), traces.Len()) - return - } - if len(want) == 0 { - return - } - js, err := traces.Data("agentRunID", time.Now()) - if nil != err { - v.Error("error creasing harvest traces data", err) - return - } - - var unmarshalled []interface{} - err = json.Unmarshal(js, &unmarshalled) - if nil != err { - v.Error("unable to unmarshal error json", err) - return - } - if "agentRunID" != unmarshalled[0].(string) { - v.Error("traces agent run id wrong", unmarshalled[0]) - return - } - gotTraces := unmarshalled[1].([]interface{}) - if len(gotTraces) != len(want) { - v.Error("number of traces in json does not match", len(gotTraces), len(want)) - return - } - for i, expected := range want { - expectTxnTrace(v, gotTraces[i], expected) - } -} - -func expectSlowQuery(t Validator, slowQuery *slowQuery, want WantSlowQuery) { - if slowQuery.Count != want.Count { - t.Error("wrong Count field", slowQuery.Count, want.Count) - } - uri, _ := slowQuery.TxnEvent.Attrs.GetAgentValue(attributeRequestURI, destTxnTrace) - validateStringField(t, "MetricName", slowQuery.DatastoreMetric, want.MetricName) - validateStringField(t, "Query", slowQuery.ParameterizedQuery, want.Query) - validateStringField(t, "TxnEvent.FinalName", slowQuery.TxnEvent.FinalName, want.TxnName) - validateStringField(t, "request.uri", uri, want.TxnURL) - validateStringField(t, "DatabaseName", slowQuery.DatabaseName, want.DatabaseName) - validateStringField(t, "Host", slowQuery.Host, want.Host) - validateStringField(t, "PortPathOrID", slowQuery.PortPathOrID, want.PortPathOrID) - expectAttributes(t, map[string]interface{}(slowQuery.QueryParameters), want.Params) -} - -// ExpectSlowQueries allows testing of slow queries. -func ExpectSlowQueries(t Validator, slowQueries *slowQueries, want []WantSlowQuery) { - if len(want) != len(slowQueries.priorityQueue) { - t.Error("wrong number of slow queries", - "expected", len(want), "got", len(slowQueries.priorityQueue)) - return - } - for _, s := range want { - idx, ok := slowQueries.lookup[s.Query] - if !ok { - t.Error("unable to find slow query", s.Query) - continue - } - expectSlowQuery(t, slowQueries.priorityQueue[idx], s) - } -} diff --git a/internal/harvest.go b/internal/harvest.go deleted file mode 100644 index bfdb3b104..000000000 --- a/internal/harvest.go +++ /dev/null @@ -1,403 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "strings" - "sync" - "time" -) - -// Harvestable is something that can be merged into a Harvest. -type Harvestable interface { - MergeIntoHarvest(h *Harvest) -} - -// HarvestTypes is a bit set used to indicate which data types are ready to be -// reported. -type HarvestTypes uint - -const ( - // HarvestMetricsTraces is the Metrics Traces type - HarvestMetricsTraces HarvestTypes = 1 << iota - // HarvestSpanEvents is the Span Event type - HarvestSpanEvents - // HarvestCustomEvents is the Custom Event type - HarvestCustomEvents - // HarvestTxnEvents is the Transaction Event type - HarvestTxnEvents - // HarvestErrorEvents is the Error Event type - HarvestErrorEvents -) - -const ( - // HarvestTypesEvents includes all Event types - HarvestTypesEvents = HarvestSpanEvents | HarvestCustomEvents | HarvestTxnEvents | HarvestErrorEvents - // HarvestTypesAll includes all harvest types - HarvestTypesAll = HarvestMetricsTraces | HarvestTypesEvents -) - -type harvestTimer struct { - periods map[HarvestTypes]time.Duration - lastHarvest map[HarvestTypes]time.Time -} - -func newHarvestTimer(now time.Time, periods map[HarvestTypes]time.Duration) *harvestTimer { - lastHarvest := make(map[HarvestTypes]time.Time, len(periods)) - for tp := range periods { - lastHarvest[tp] = now - } - return &harvestTimer{periods: periods, lastHarvest: lastHarvest} -} - -func (timer *harvestTimer) ready(now time.Time) (ready HarvestTypes) { - for tp, period := range timer.periods { - if deadline := timer.lastHarvest[tp].Add(period); now.After(deadline) { - timer.lastHarvest[tp] = deadline - ready |= tp - } - } - return -} - -// Harvest contains collected data. -type Harvest struct { - timer *harvestTimer - - Metrics *metricTable - ErrorTraces harvestErrors - TxnTraces *harvestTraces - SlowSQLs *slowQueries - SpanEvents *spanEvents - CustomEvents *customEvents - TxnEvents *txnEvents - ErrorEvents *errorEvents -} - -const ( - // txnEventPayloadlimit is the maximum number of events that should be - // sent up in one post. - txnEventPayloadlimit = 5000 -) - -// Ready returns a new Harvest which contains the data types ready for harvest, -// or nil if no data is ready for harvest. -func (h *Harvest) Ready(now time.Time) *Harvest { - ready := &Harvest{} - - types := h.timer.ready(now) - if 0 == types { - return nil - } - - if 0 != types&HarvestCustomEvents { - h.Metrics.addCount(customEventsSeen, h.CustomEvents.NumSeen(), forced) - h.Metrics.addCount(customEventsSent, h.CustomEvents.NumSaved(), forced) - ready.CustomEvents = h.CustomEvents - h.CustomEvents = newCustomEvents(h.CustomEvents.capacity()) - } - if 0 != types&HarvestTxnEvents { - h.Metrics.addCount(txnEventsSeen, h.TxnEvents.NumSeen(), forced) - h.Metrics.addCount(txnEventsSent, h.TxnEvents.NumSaved(), forced) - ready.TxnEvents = h.TxnEvents - h.TxnEvents = newTxnEvents(h.TxnEvents.capacity()) - } - if 0 != types&HarvestErrorEvents { - h.Metrics.addCount(errorEventsSeen, h.ErrorEvents.NumSeen(), forced) - h.Metrics.addCount(errorEventsSent, h.ErrorEvents.NumSaved(), forced) - ready.ErrorEvents = h.ErrorEvents - h.ErrorEvents = newErrorEvents(h.ErrorEvents.capacity()) - } - if 0 != types&HarvestSpanEvents { - h.Metrics.addCount(spanEventsSeen, h.SpanEvents.NumSeen(), forced) - h.Metrics.addCount(spanEventsSent, h.SpanEvents.NumSaved(), forced) - ready.SpanEvents = h.SpanEvents - h.SpanEvents = newSpanEvents(h.SpanEvents.capacity()) - } - // NOTE! Metrics must happen after the event harvest conditionals to - // ensure that the metrics contain the event supportability metrics. - if 0 != types&HarvestMetricsTraces { - ready.Metrics = h.Metrics - ready.ErrorTraces = h.ErrorTraces - ready.SlowSQLs = h.SlowSQLs - ready.TxnTraces = h.TxnTraces - h.Metrics = newMetricTable(maxMetrics, now) - h.ErrorTraces = newHarvestErrors(maxHarvestErrors) - h.SlowSQLs = newSlowQueries(maxHarvestSlowSQLs) - h.TxnTraces = newHarvestTraces() - } - return ready -} - -// Payloads returns a slice of payload creators. -func (h *Harvest) Payloads(splitLargeTxnEvents bool) (ps []PayloadCreator) { - if nil == h { - return - } - if nil != h.CustomEvents { - ps = append(ps, h.CustomEvents) - } - if nil != h.ErrorEvents { - ps = append(ps, h.ErrorEvents) - } - if nil != h.SpanEvents { - ps = append(ps, h.SpanEvents) - } - if nil != h.Metrics { - ps = append(ps, h.Metrics) - } - if nil != h.ErrorTraces { - ps = append(ps, h.ErrorTraces) - } - if nil != h.TxnTraces { - ps = append(ps, h.TxnTraces) - } - if nil != h.SlowSQLs { - ps = append(ps, h.SlowSQLs) - } - if nil != h.TxnEvents { - if splitLargeTxnEvents { - ps = append(ps, h.TxnEvents.payloads(txnEventPayloadlimit)...) - } else { - ps = append(ps, h.TxnEvents) - } - } - return -} - -// MaxTxnEventer returns the maximum number of Transaction Events that should be reported per period -type MaxTxnEventer interface { - MaxTxnEvents() int -} - -// HarvestConfigurer contains information about the configured number of various -// types of events as well as the Faster Event Harvest report period. -// It is implemented by AppRun and DfltHarvestCfgr. -type HarvestConfigurer interface { - // ReportPeriods returns a map from the bitset of harvest types to the period that those types should be reported - ReportPeriods() map[HarvestTypes]time.Duration - // MaxSpanEvents returns the maximum number of Span Events that should be reported per period - MaxSpanEvents() int - // MaxCustomEvents returns the maximum number of Custom Events that should be reported per period - MaxCustomEvents() int - // MaxErrorEvents returns the maximum number of Error Events that should be reported per period - MaxErrorEvents() int - MaxTxnEventer -} - -// NewHarvest returns a new Harvest. -func NewHarvest(now time.Time, configurer HarvestConfigurer) *Harvest { - return &Harvest{ - timer: newHarvestTimer(now, configurer.ReportPeriods()), - Metrics: newMetricTable(maxMetrics, now), - ErrorTraces: newHarvestErrors(maxHarvestErrors), - TxnTraces: newHarvestTraces(), - SlowSQLs: newSlowQueries(maxHarvestSlowSQLs), - SpanEvents: newSpanEvents(configurer.MaxSpanEvents()), - CustomEvents: newCustomEvents(configurer.MaxCustomEvents()), - TxnEvents: newTxnEvents(configurer.MaxTxnEvents()), - ErrorEvents: newErrorEvents(configurer.MaxErrorEvents()), - } -} - -var ( - trackMutex sync.Mutex - trackMetrics []string -) - -// TrackUsage helps track which integration packages are used. -func TrackUsage(s ...string) { - trackMutex.Lock() - defer trackMutex.Unlock() - - m := "Supportability/" + strings.Join(s, "/") - trackMetrics = append(trackMetrics, m) -} - -func createTrackUsageMetrics(metrics *metricTable) { - trackMutex.Lock() - defer trackMutex.Unlock() - - for _, m := range trackMetrics { - metrics.addSingleCount(m, forced) - } -} - -// CreateFinalMetrics creates extra metrics at harvest time. -func (h *Harvest) CreateFinalMetrics(reply *ConnectReply, hc HarvestConfigurer) { - if nil == h { - return - } - // Metrics will be non-nil when harvesting metrics (regardless of - // whether or not there are any metrics to send). - if nil == h.Metrics { - return - } - - h.Metrics.addSingleCount(instanceReporting, forced) - - // Configurable event harvest supportability metrics: - // https://source.datanerd.us/agents/agent-specs/blob/master/Connect-LEGACY.md#event-harvest-config - period := reply.ConfigurablePeriod() - h.Metrics.addDuration(supportReportPeriod, "", period, period, forced) - h.Metrics.addValue(supportTxnEventLimit, "", float64(hc.MaxTxnEvents()), forced) - h.Metrics.addValue(supportCustomEventLimit, "", float64(hc.MaxCustomEvents()), forced) - h.Metrics.addValue(supportErrorEventLimit, "", float64(hc.MaxErrorEvents()), forced) - h.Metrics.addValue(supportSpanEventLimit, "", float64(hc.MaxSpanEvents()), forced) - - createTrackUsageMetrics(h.Metrics) - - h.Metrics = h.Metrics.ApplyRules(reply.MetricRules) -} - -// PayloadCreator is a data type in the harvest. -type PayloadCreator interface { - // In the event of a rpm request failure (hopefully simply an - // intermittent collector issue) the payload may be merged into the next - // time period's harvest. - Harvestable - // Data prepares JSON in the format expected by the collector endpoint. - // This method should return (nil, nil) if the payload is empty and no - // rpm request is necessary. - Data(agentRunID string, harvestStart time.Time) ([]byte, error) - // EndpointMethod is used for the "method" query parameter when posting - // the data. - EndpointMethod() string -} - -func supportMetric(metrics *metricTable, b bool, metricName string) { - if b { - metrics.addSingleCount(metricName, forced) - } -} - -// CreateTxnMetrics creates metrics for a transaction. -func CreateTxnMetrics(args *TxnData, metrics *metricTable) { - withoutFirstSegment := removeFirstSegment(args.FinalName) - - // Duration Metrics - var durationRollup string - var totalTimeRollup string - if args.IsWeb { - durationRollup = webRollup - totalTimeRollup = totalTimeWeb - metrics.addDuration(dispatcherMetric, "", args.Duration, 0, forced) - } else { - durationRollup = backgroundRollup - totalTimeRollup = totalTimeBackground - } - - metrics.addDuration(args.FinalName, "", args.Duration, 0, forced) - metrics.addDuration(durationRollup, "", args.Duration, 0, forced) - - metrics.addDuration(totalTimeRollup, "", args.TotalTime, args.TotalTime, forced) - metrics.addDuration(totalTimeRollup+"/"+withoutFirstSegment, "", args.TotalTime, args.TotalTime, unforced) - - // Better CAT Metrics - if cat := args.BetterCAT; cat.Enabled { - caller := callerUnknown - if nil != cat.Inbound { - caller = cat.Inbound.payloadCaller - } - m := durationByCallerMetric(caller) - metrics.addDuration(m.all, "", args.Duration, args.Duration, unforced) - metrics.addDuration(m.webOrOther(args.IsWeb), "", args.Duration, args.Duration, unforced) - - // Transport Duration Metric - if nil != cat.Inbound { - d := cat.Inbound.TransportDuration - m = transportDurationMetric(caller) - metrics.addDuration(m.all, "", d, d, unforced) - metrics.addDuration(m.webOrOther(args.IsWeb), "", d, d, unforced) - } - - // CAT Error Metrics - if args.HasErrors() { - m = errorsByCallerMetric(caller) - metrics.addSingleCount(m.all, unforced) - metrics.addSingleCount(m.webOrOther(args.IsWeb), unforced) - } - - supportMetric(metrics, args.AcceptPayloadSuccess, supportTracingAcceptSuccess) - supportMetric(metrics, args.AcceptPayloadException, supportTracingAcceptException) - supportMetric(metrics, args.AcceptPayloadParseException, supportTracingAcceptParseException) - supportMetric(metrics, args.AcceptPayloadCreateBeforeAccept, supportTracingCreateBeforeAccept) - supportMetric(metrics, args.AcceptPayloadIgnoredMultiple, supportTracingIgnoredMultiple) - supportMetric(metrics, args.AcceptPayloadIgnoredVersion, supportTracingIgnoredVersion) - supportMetric(metrics, args.AcceptPayloadUntrustedAccount, supportTracingAcceptUntrustedAccount) - supportMetric(metrics, args.AcceptPayloadNullPayload, supportTracingAcceptNull) - supportMetric(metrics, args.CreatePayloadSuccess, supportTracingCreatePayloadSuccess) - supportMetric(metrics, args.CreatePayloadException, supportTracingCreatePayloadException) - } - - // Apdex Metrics - if args.Zone != ApdexNone { - metrics.addApdex(apdexRollup, "", args.ApdexThreshold, args.Zone, forced) - - mname := apdexPrefix + withoutFirstSegment - metrics.addApdex(mname, "", args.ApdexThreshold, args.Zone, unforced) - } - - // Error Metrics - if args.HasErrors() { - metrics.addSingleCount(errorsRollupMetric.all, forced) - metrics.addSingleCount(errorsRollupMetric.webOrOther(args.IsWeb), forced) - metrics.addSingleCount(errorsPrefix+args.FinalName, forced) - } - - // Queueing Metrics - if args.Queuing > 0 { - metrics.addDuration(queueMetric, "", args.Queuing, args.Queuing, forced) - } -} - -// DfltHarvestCfgr implements HarvestConfigurer for internal test cases, and for situations where we don't -// have a ConnectReply, such as for serverless harvests -type DfltHarvestCfgr struct { - reportPeriods map[HarvestTypes]time.Duration - maxTxnEvents *uint - maxSpanEvents *uint - maxCustomEvents *uint - maxErrorEvents *uint -} - -// ReportPeriods returns a map from the bitset of harvest types to the period that those types should be reported -func (d *DfltHarvestCfgr) ReportPeriods() map[HarvestTypes]time.Duration { - if d.reportPeriods != nil { - return d.reportPeriods - } - return map[HarvestTypes]time.Duration{HarvestTypesAll: FixedHarvestPeriod} -} - -// MaxTxnEvents returns the maximum number of Transaction Events that should be reported per period -func (d *DfltHarvestCfgr) MaxTxnEvents() int { - if d.maxTxnEvents != nil { - return int(*d.maxTxnEvents) - } - return MaxTxnEvents -} - -// MaxSpanEvents returns the maximum number of Span Events that should be reported per period -func (d *DfltHarvestCfgr) MaxSpanEvents() int { - if d.maxSpanEvents != nil { - return int(*d.maxSpanEvents) - } - return MaxSpanEvents -} - -// MaxCustomEvents returns the maximum number of Custom Events that should be reported per period -func (d *DfltHarvestCfgr) MaxCustomEvents() int { - if d.maxCustomEvents != nil { - return int(*d.maxCustomEvents) - } - return MaxCustomEvents -} - -// MaxErrorEvents returns the maximum number of Error Events that should be reported per period -func (d *DfltHarvestCfgr) MaxErrorEvents() int { - if d.maxErrorEvents != nil { - return int(*d.maxErrorEvents) - } - return MaxErrorEvents -} diff --git a/internal/harvest_test.go b/internal/harvest_test.go deleted file mode 100644 index dd606b6b7..000000000 --- a/internal/harvest_test.go +++ /dev/null @@ -1,878 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "testing" - "time" -) - -func TestHarvestTimerAllFixed(t *testing.T) { - now := time.Now() - harvest := NewHarvest(now, &DfltHarvestCfgr{}) - timer := harvest.timer - for _, tc := range []struct { - Elapsed time.Duration - Expect HarvestTypes - }{ - {60 * time.Second, 0}, - {61 * time.Second, HarvestTypesAll}, - {62 * time.Second, 0}, - {120 * time.Second, 0}, - {121 * time.Second, HarvestTypesAll}, - {122 * time.Second, 0}, - } { - if ready := timer.ready(now.Add(tc.Elapsed)); ready != tc.Expect { - t.Error(tc.Elapsed, ready, tc.Expect) - } - } -} - -var one uint = 1 -var two uint = 2 -var three uint = 3 -var four uint = 4 - -func TestHarvestTimerAllConfigurable(t *testing.T) { - now := time.Now() - harvest := NewHarvest(now, &DfltHarvestCfgr{ - reportPeriods: map[HarvestTypes]time.Duration{ - HarvestMetricsTraces: FixedHarvestPeriod, - HarvestTypesEvents: time.Second * 30, - }, - maxTxnEvents: &one, - maxCustomEvents: &two, - maxSpanEvents: &three, - maxErrorEvents: &four, - }) - timer := harvest.timer - for _, tc := range []struct { - Elapsed time.Duration - Expect HarvestTypes - }{ - {30 * time.Second, 0}, - {31 * time.Second, HarvestTypesEvents}, - {32 * time.Second, 0}, - {61 * time.Second, HarvestTypesAll}, - {62 * time.Second, 0}, - {91 * time.Second, HarvestTypesEvents}, - {92 * time.Second, 0}, - } { - if ready := timer.ready(now.Add(tc.Elapsed)); ready != tc.Expect { - t.Error(tc.Elapsed, ready, tc.Expect) - } - } -} - -func TestCreateFinalMetrics(t *testing.T) { - now := time.Now() - - // If the harvest or metrics is nil then CreateFinalMetrics should - // not panic. - var nilHarvest *Harvest - nilHarvest.CreateFinalMetrics(nil, &DfltHarvestCfgr{}) - emptyHarvest := &Harvest{} - emptyHarvest.CreateFinalMetrics(nil, &DfltHarvestCfgr{}) - - replyJSON := []byte(`{"return_value":{ - "metric_name_rules":[{ - "match_expression": "rename_me", - "replacement": "been_renamed" - }], - "event_harvest_config":{ - "report_period_ms": 2000, - "harvest_limits": { - "analytic_event_data": 22, - "custom_event_data": 33, - "error_event_data": 44, - "span_event_data": 55 - } - } - }}`) - reply, err := ConstructConnectReply(replyJSON, PreconnectReply{}) - if err != nil { - t.Fatal(err) - } - var txnEvents uint = 22 - var customEvents uint = 33 - var errorEvents uint = 44 - var spanEvents uint = 55 - cfgr := &DfltHarvestCfgr{ - reportPeriods: map[HarvestTypes]time.Duration{ - HarvestMetricsTraces: FixedHarvestPeriod, - HarvestTypesEvents: time.Second * 2, - }, - maxTxnEvents: &txnEvents, - maxCustomEvents: &customEvents, - maxErrorEvents: &errorEvents, - maxSpanEvents: &spanEvents, - } - h := NewHarvest(now, cfgr) - h.Metrics.addCount("rename_me", 1.0, unforced) - h.CreateFinalMetrics(reply, cfgr) - ExpectMetrics(t, h.Metrics, []WantMetric{ - {instanceReporting, "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"been_renamed", "", false, []float64{1.0, 0, 0, 0, 0, 0}}, - {"Supportability/EventHarvest/ReportPeriod", "", true, []float64{1, 2, 2, 2, 2, 2 * 2}}, - {"Supportability/EventHarvest/AnalyticEventData/HarvestLimit", "", true, []float64{1, 22, 22, 22, 22, 22 * 22}}, - {"Supportability/EventHarvest/CustomEventData/HarvestLimit", "", true, []float64{1, 33, 33, 33, 33, 33 * 33}}, - {"Supportability/EventHarvest/ErrorEventData/HarvestLimit", "", true, []float64{1, 44, 44, 44, 44, 44 * 44}}, - {"Supportability/EventHarvest/SpanEventData/HarvestLimit", "", true, []float64{1, 55, 55, 55, 55, 55 * 55}}, - }) - - // Test again without any metric rules or event_harvest_config. - - replyJSON = []byte(`{"return_value":{ - }}`) - reply, err = ConstructConnectReply(replyJSON, PreconnectReply{}) - if err != nil { - t.Fatal(err) - } - h = NewHarvest(now, &DfltHarvestCfgr{}) - h.Metrics.addCount("rename_me", 1.0, unforced) - h.CreateFinalMetrics(reply, &DfltHarvestCfgr{}) - ExpectMetrics(t, h.Metrics, []WantMetric{ - {instanceReporting, "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"rename_me", "", false, []float64{1.0, 0, 0, 0, 0, 0}}, - {"Supportability/EventHarvest/ReportPeriod", "", true, []float64{1, 60, 60, 60, 60, 60 * 60}}, - {"Supportability/EventHarvest/AnalyticEventData/HarvestLimit", "", true, []float64{1, 10 * 1000, 10 * 1000, 10 * 1000, 10 * 1000, 10 * 1000 * 10 * 1000}}, - {"Supportability/EventHarvest/CustomEventData/HarvestLimit", "", true, []float64{1, 10 * 1000, 10 * 1000, 10 * 1000, 10 * 1000, 10 * 1000 * 10 * 1000}}, - {"Supportability/EventHarvest/ErrorEventData/HarvestLimit", "", true, []float64{1, 100, 100, 100, 100, 100 * 100}}, - {"Supportability/EventHarvest/SpanEventData/HarvestLimit", "", true, []float64{1, 1000, 1000, 1000, 1000, 1000 * 1000}}, - }) -} - -func TestEmptyPayloads(t *testing.T) { - h := NewHarvest(time.Now(), &DfltHarvestCfgr{}) - payloads := h.Payloads(true) - if len(payloads) != 8 { - t.Error(len(payloads)) - } - for _, p := range payloads { - d, err := p.Data("agentRunID", time.Now()) - if d != nil || err != nil { - t.Error(d, err) - } - } -} -func TestPayloadsNilHarvest(t *testing.T) { - var nilHarvest *Harvest - payloads := nilHarvest.Payloads(true) - if len(payloads) != 0 { - t.Error(len(payloads)) - } -} - -func TestPayloadsEmptyHarvest(t *testing.T) { - h := &Harvest{} - payloads := h.Payloads(true) - if len(payloads) != 0 { - t.Error(len(payloads)) - } -} - -func TestHarvestNothingReady(t *testing.T) { - now := time.Now() - h := NewHarvest(now, &DfltHarvestCfgr{}) - ready := h.Ready(now.Add(10 * time.Second)) - if ready != nil { - t.Error("harvest should be nil") - } - payloads := ready.Payloads(true) - if len(payloads) != 0 { - t.Error(payloads) - } - ExpectMetrics(t, h.Metrics, []WantMetric{}) -} - -func TestHarvestCustomEventsReady(t *testing.T) { - now := time.Now() - fixedHarvestTypes := HarvestMetricsTraces & HarvestTxnEvents & HarvestSpanEvents & HarvestErrorEvents - h := NewHarvest(now, &DfltHarvestCfgr{ - reportPeriods: map[HarvestTypes]time.Duration{ - fixedHarvestTypes: FixedHarvestPeriod, - HarvestCustomEvents: time.Second * 5, - }, - maxCustomEvents: &three, - }) - params := map[string]interface{}{"zip": 1} - ce, _ := CreateCustomEvent("myEvent", params, time.Now()) - h.CustomEvents.Add(ce) - ready := h.Ready(now.Add(10 * time.Second)) - payloads := ready.Payloads(true) - if len(payloads) != 1 { - t.Fatal(payloads) - } - p := payloads[0] - if m := p.EndpointMethod(); m != "custom_event_data" { - t.Error(m) - } - data, err := p.Data("agentRunID", now) - if nil != err || nil == data { - t.Error(err, data) - } - if h.CustomEvents.capacity() != 3 || h.CustomEvents.NumSaved() != 0 { - t.Fatal("custom events not correctly reset") - } - ExpectCustomEvents(t, ready.CustomEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{"type": "myEvent", "timestamp": MatchAnything}, - UserAttributes: params, - }}) - ExpectMetrics(t, h.Metrics, []WantMetric{ - {customEventsSeen, "", true, []float64{1, 0, 0, 0, 0, 0}}, - {customEventsSent, "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) -} - -func TestHarvestTxnEventsReady(t *testing.T) { - now := time.Now() - fixedHarvestTypes := HarvestMetricsTraces & HarvestCustomEvents & HarvestSpanEvents & HarvestErrorEvents - h := NewHarvest(now, &DfltHarvestCfgr{ - reportPeriods: map[HarvestTypes]time.Duration{ - fixedHarvestTypes: FixedHarvestPeriod, - HarvestTxnEvents: time.Second * 5, - }, - maxTxnEvents: &three, - }) - h.TxnEvents.AddTxnEvent(&TxnEvent{ - FinalName: "finalName", - Start: time.Now(), - Duration: 1 * time.Second, - TotalTime: 2 * time.Second, - }, 0) - ready := h.Ready(now.Add(10 * time.Second)) - payloads := ready.Payloads(true) - if len(payloads) != 1 { - t.Fatal(payloads) - } - p := payloads[0] - if m := p.EndpointMethod(); m != "analytic_event_data" { - t.Error(m) - } - data, err := p.Data("agentRunID", now) - if nil != err || nil == data { - t.Error(err, data) - } - if h.TxnEvents.capacity() != 3 || h.TxnEvents.NumSaved() != 0 { - t.Fatal("txn events not correctly reset") - } - ExpectTxnEvents(t, ready.TxnEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "finalName", - "totalTime": 2.0, - }, - }}) - ExpectMetrics(t, h.Metrics, []WantMetric{ - {txnEventsSeen, "", true, []float64{1, 0, 0, 0, 0, 0}}, - {txnEventsSent, "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) -} - -func TestHarvestErrorEventsReady(t *testing.T) { - now := time.Now() - fixedHarvestTypes := HarvestMetricsTraces & HarvestCustomEvents & HarvestSpanEvents & HarvestTxnEvents - h := NewHarvest(now, &DfltHarvestCfgr{ - reportPeriods: map[HarvestTypes]time.Duration{ - fixedHarvestTypes: FixedHarvestPeriod, - HarvestErrorEvents: time.Second * 5, - }, - maxErrorEvents: &three, - }) - h.ErrorEvents.Add(&ErrorEvent{ - ErrorData: ErrorData{Klass: "klass", Msg: "msg", When: time.Now()}, - TxnEvent: TxnEvent{FinalName: "finalName", Duration: 1 * time.Second}, - }, 0) - ready := h.Ready(now.Add(10 * time.Second)) - payloads := ready.Payloads(true) - if len(payloads) != 1 { - t.Fatal(payloads) - } - p := payloads[0] - if m := p.EndpointMethod(); m != "error_event_data" { - t.Error(m) - } - data, err := p.Data("agentRunID", now) - if nil != err || nil == data { - t.Error(err, data) - } - if h.ErrorEvents.capacity() != 3 || h.ErrorEvents.NumSaved() != 0 { - t.Fatal("error events not correctly reset") - } - ExpectErrorEvents(t, ready.ErrorEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "klass", - "error.message": "msg", - "transactionName": "finalName", - }, - }}) - ExpectMetrics(t, h.Metrics, []WantMetric{ - {errorEventsSeen, "", true, []float64{1, 0, 0, 0, 0, 0}}, - {errorEventsSent, "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) -} - -func TestHarvestSpanEventsReady(t *testing.T) { - now := time.Now() - fixedHarvestTypes := HarvestMetricsTraces & HarvestCustomEvents & HarvestTxnEvents & HarvestErrorEvents - h := NewHarvest(now, &DfltHarvestCfgr{ - reportPeriods: map[HarvestTypes]time.Duration{ - fixedHarvestTypes: FixedHarvestPeriod, - HarvestSpanEvents: time.Second * 5, - }, - maxSpanEvents: &three, - }) - h.SpanEvents.addEventPopulated(&sampleSpanEvent) - ready := h.Ready(now.Add(10 * time.Second)) - payloads := ready.Payloads(true) - if len(payloads) != 1 { - t.Fatal(payloads) - } - p := payloads[0] - if m := p.EndpointMethod(); m != "span_event_data" { - t.Error(m) - } - data, err := p.Data("agentRunID", now) - if nil != err || nil == data { - t.Error(err, data) - } - if h.SpanEvents.capacity() != 3 || h.SpanEvents.NumSaved() != 0 { - t.Fatal("span events not correctly reset") - } - ExpectSpanEvents(t, ready.SpanEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "type": "Span", - "name": "myName", - "sampled": true, - "priority": 0.5, - "category": spanCategoryGeneric, - "nr.entryPoint": true, - "guid": "guid", - "transactionId": "txn-id", - "traceId": "trace-id", - }, - }}) - ExpectMetrics(t, h.Metrics, []WantMetric{ - {spanEventsSeen, "", true, []float64{1, 0, 0, 0, 0, 0}}, - {spanEventsSent, "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) -} - -func TestHarvestMetricsTracesReady(t *testing.T) { - now := time.Now() - h := NewHarvest(now, &DfltHarvestCfgr{ - reportPeriods: map[HarvestTypes]time.Duration{ - HarvestMetricsTraces: FixedHarvestPeriod, - HarvestTypesEvents: time.Second * 65, - }, - maxTxnEvents: &one, - maxCustomEvents: &one, - maxErrorEvents: &one, - maxSpanEvents: &one, - }) - h.Metrics.addCount("zip", 1, forced) - - ers := NewTxnErrors(10) - ers.Add(ErrorData{When: time.Now(), Msg: "msg", Klass: "klass", Stack: GetStackTrace()}) - MergeTxnErrors(&h.ErrorTraces, ers, TxnEvent{FinalName: "finalName", Attrs: nil}) - - h.TxnTraces.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: time.Now(), - Duration: 20 * time.Second, - TotalTime: 30 * time.Second, - FinalName: "WebTransaction/Go/hello", - }, - Trace: TxnTrace{}, - }) - - slows := newSlowQueries(maxTxnSlowQueries) - slows.observeInstance(slowQueryInstance{ - Duration: 2 * time.Second, - DatastoreMetric: "Datastore/statement/MySQL/users/INSERT", - ParameterizedQuery: "INSERT users", - }) - h.SlowSQLs.Merge(slows, TxnEvent{FinalName: "finalName", Attrs: nil}) - - ready := h.Ready(now.Add(61 * time.Second)) - payloads := ready.Payloads(true) - if len(payloads) != 4 { - t.Fatal(payloads) - } - - ExpectMetrics(t, ready.Metrics, []WantMetric{ - {"zip", "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) - ExpectMetrics(t, h.Metrics, []WantMetric{}) - - ExpectErrors(t, ready.ErrorTraces, []WantError{{ - TxnName: "finalName", - Msg: "msg", - Klass: "klass", - }}) - ExpectErrors(t, h.ErrorTraces, []WantError{}) - - ExpectSlowQueries(t, ready.SlowSQLs, []WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT users", - TxnName: "finalName", - }}) - ExpectSlowQueries(t, h.SlowSQLs, []WantSlowQuery{}) - - ExpectTxnTraces(t, ready.TxnTraces, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - }}) - ExpectTxnTraces(t, h.TxnTraces, []WantTxnTrace{}) -} - -func TestMergeFailedHarvest(t *testing.T) { - start1 := time.Now() - start2 := start1.Add(1 * time.Minute) - - h := NewHarvest(start1, &DfltHarvestCfgr{}) - h.Metrics.addCount("zip", 1, forced) - h.TxnEvents.AddTxnEvent(&TxnEvent{ - FinalName: "finalName", - Start: time.Now(), - Duration: 1 * time.Second, - TotalTime: 2 * time.Second, - }, 0) - customEventParams := map[string]interface{}{"zip": 1} - ce, err := CreateCustomEvent("myEvent", customEventParams, time.Now()) - if nil != err { - t.Fatal(err) - } - h.CustomEvents.Add(ce) - h.ErrorEvents.Add(&ErrorEvent{ - ErrorData: ErrorData{ - Klass: "klass", - Msg: "msg", - When: time.Now(), - }, - TxnEvent: TxnEvent{ - FinalName: "finalName", - Duration: 1 * time.Second, - }, - }, 0) - - ers := NewTxnErrors(10) - ers.Add(ErrorData{ - When: time.Now(), - Msg: "msg", - Klass: "klass", - Stack: GetStackTrace(), - }) - MergeTxnErrors(&h.ErrorTraces, ers, TxnEvent{ - FinalName: "finalName", - Attrs: nil, - }) - h.SpanEvents.addEventPopulated(&sampleSpanEvent) - - if start1 != h.Metrics.metricPeriodStart { - t.Error(h.Metrics.metricPeriodStart) - } - if 0 != h.Metrics.failedHarvests { - t.Error(h.Metrics.failedHarvests) - } - if 0 != h.CustomEvents.analyticsEvents.failedHarvests { - t.Error(h.CustomEvents.analyticsEvents.failedHarvests) - } - if 0 != h.TxnEvents.analyticsEvents.failedHarvests { - t.Error(h.TxnEvents.analyticsEvents.failedHarvests) - } - if 0 != h.ErrorEvents.analyticsEvents.failedHarvests { - t.Error(h.ErrorEvents.analyticsEvents.failedHarvests) - } - if 0 != h.SpanEvents.analyticsEvents.failedHarvests { - t.Error(h.SpanEvents.analyticsEvents.failedHarvests) - } - ExpectMetrics(t, h.Metrics, []WantMetric{ - {"zip", "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) - ExpectCustomEvents(t, h.CustomEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "type": "myEvent", - "timestamp": MatchAnything, - }, - UserAttributes: customEventParams, - }}) - ExpectErrorEvents(t, h.ErrorEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "klass", - "error.message": "msg", - "transactionName": "finalName", - }, - }}) - ExpectTxnEvents(t, h.TxnEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "finalName", - "totalTime": 2.0, - }, - }}) - ExpectSpanEvents(t, h.SpanEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "type": "Span", - "name": "myName", - "sampled": true, - "priority": 0.5, - "category": spanCategoryGeneric, - "nr.entryPoint": true, - "guid": "guid", - "transactionId": "txn-id", - "traceId": "trace-id", - }, - }}) - ExpectErrors(t, h.ErrorTraces, []WantError{{ - TxnName: "finalName", - Msg: "msg", - Klass: "klass", - }}) - - nextHarvest := NewHarvest(start2, &DfltHarvestCfgr{}) - if start2 != nextHarvest.Metrics.metricPeriodStart { - t.Error(nextHarvest.Metrics.metricPeriodStart) - } - payloads := h.Payloads(true) - for _, p := range payloads { - p.MergeIntoHarvest(nextHarvest) - } - - if start1 != nextHarvest.Metrics.metricPeriodStart { - t.Error(nextHarvest.Metrics.metricPeriodStart) - } - if 1 != nextHarvest.Metrics.failedHarvests { - t.Error(nextHarvest.Metrics.failedHarvests) - } - if 1 != nextHarvest.CustomEvents.analyticsEvents.failedHarvests { - t.Error(nextHarvest.CustomEvents.analyticsEvents.failedHarvests) - } - if 1 != nextHarvest.TxnEvents.analyticsEvents.failedHarvests { - t.Error(nextHarvest.TxnEvents.analyticsEvents.failedHarvests) - } - if 1 != nextHarvest.ErrorEvents.analyticsEvents.failedHarvests { - t.Error(nextHarvest.ErrorEvents.analyticsEvents.failedHarvests) - } - if 1 != nextHarvest.SpanEvents.analyticsEvents.failedHarvests { - t.Error(nextHarvest.SpanEvents.analyticsEvents.failedHarvests) - } - ExpectMetrics(t, nextHarvest.Metrics, []WantMetric{ - {"zip", "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) - ExpectCustomEvents(t, nextHarvest.CustomEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "type": "myEvent", - "timestamp": MatchAnything, - }, - UserAttributes: customEventParams, - }}) - ExpectErrorEvents(t, nextHarvest.ErrorEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "klass", - "error.message": "msg", - "transactionName": "finalName", - }, - }}) - ExpectTxnEvents(t, nextHarvest.TxnEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "finalName", - "totalTime": 2.0, - }, - }}) - ExpectSpanEvents(t, h.SpanEvents, []WantEvent{{ - Intrinsics: map[string]interface{}{ - "type": "Span", - "name": "myName", - "sampled": true, - "priority": 0.5, - "category": spanCategoryGeneric, - "nr.entryPoint": true, - "guid": "guid", - "transactionId": "txn-id", - "traceId": "trace-id", - }, - }}) - ExpectErrors(t, nextHarvest.ErrorTraces, []WantError{}) -} - -func TestCreateTxnMetrics(t *testing.T) { - txnErr := &ErrorData{} - txnErrors := []*ErrorData{txnErr} - webName := "WebTransaction/zip/zap" - backgroundName := "OtherTransaction/zip/zap" - args := &TxnData{} - args.Duration = 123 * time.Second - args.TotalTime = 150 * time.Second - args.ApdexThreshold = 2 * time.Second - - args.BetterCAT.Enabled = true - - args.FinalName = webName - args.IsWeb = true - args.Errors = txnErrors - args.Zone = ApdexTolerating - metrics := newMetricTable(100, time.Now()) - CreateTxnMetrics(args, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {webName, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {webRollup, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {dispatcherMetric, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {"WebTransactionTotalTime", "", true, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"WebTransactionTotalTime/zip/zap", "", false, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"Errors/all", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Errors/allWeb", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Errors/" + webName, "", true, []float64{1, 0, 0, 0, 0, 0}}, - {apdexRollup, "", true, []float64{0, 1, 0, 2, 2, 0}}, - {"Apdex/zip/zap", "", false, []float64{0, 1, 0, 2, 2, 0}}, - {"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", "", false, []float64{1, 123, 123, 123, 123, 123 * 123}}, - {"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", "", false, []float64{1, 123, 123, 123, 123, 123 * 123}}, - {"ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", "", false, []float64{1, 0, 0, 0, 0, 0}}, - {"ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", "", false, []float64{1, 0, 0, 0, 0, 0}}, - }) - - args.FinalName = webName - args.IsWeb = true - args.Errors = nil - args.Zone = ApdexTolerating - metrics = newMetricTable(100, time.Now()) - CreateTxnMetrics(args, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {webName, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {webRollup, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {dispatcherMetric, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {"WebTransactionTotalTime", "", true, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"WebTransactionTotalTime/zip/zap", "", false, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {apdexRollup, "", true, []float64{0, 1, 0, 2, 2, 0}}, - {"Apdex/zip/zap", "", false, []float64{0, 1, 0, 2, 2, 0}}, - {"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", "", false, []float64{1, 123, 123, 123, 123, 123 * 123}}, - {"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", "", false, []float64{1, 123, 123, 123, 123, 123 * 123}}, - }) - - args.FinalName = backgroundName - args.IsWeb = false - args.Errors = txnErrors - args.Zone = ApdexNone - metrics = newMetricTable(100, time.Now()) - CreateTxnMetrics(args, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {backgroundName, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {backgroundRollup, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {"OtherTransactionTotalTime", "", true, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"OtherTransactionTotalTime/zip/zap", "", false, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"Errors/all", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Errors/allOther", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Errors/" + backgroundName, "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", "", false, []float64{1, 123, 123, 123, 123, 123 * 123}}, - {"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", "", false, []float64{1, 123, 123, 123, 123, 123 * 123}}, - {"ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", "", false, []float64{1, 0, 0, 0, 0, 0}}, - {"ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allOther", "", false, []float64{1, 0, 0, 0, 0, 0}}, - }) - - args.FinalName = backgroundName - args.IsWeb = false - args.Errors = nil - args.Zone = ApdexNone - metrics = newMetricTable(100, time.Now()) - CreateTxnMetrics(args, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {backgroundName, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {backgroundRollup, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {"OtherTransactionTotalTime", "", true, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"OtherTransactionTotalTime/zip/zap", "", false, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", "", false, []float64{1, 123, 123, 123, 123, 123 * 123}}, - {"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", "", false, []float64{1, 123, 123, 123, 123, 123 * 123}}, - }) - -} - -func TestHarvestSplitTxnEvents(t *testing.T) { - now := time.Now() - h := NewHarvest(now, &DfltHarvestCfgr{}) - for i := 0; i < MaxTxnEvents; i++ { - h.TxnEvents.AddTxnEvent(&TxnEvent{}, Priority(float32(i))) - } - - payloadsWithSplit := h.Payloads(true) - payloadsWithoutSplit := h.Payloads(false) - - if len(payloadsWithSplit) != 9 { - t.Error(len(payloadsWithSplit)) - } - if len(payloadsWithoutSplit) != 8 { - t.Error(len(payloadsWithoutSplit)) - } -} - -func TestCreateTxnMetricsOldCAT(t *testing.T) { - txnErr := &ErrorData{} - txnErrors := []*ErrorData{txnErr} - webName := "WebTransaction/zip/zap" - backgroundName := "OtherTransaction/zip/zap" - args := &TxnData{} - args.Duration = 123 * time.Second - args.TotalTime = 150 * time.Second - args.ApdexThreshold = 2 * time.Second - - // When BetterCAT is disabled, affirm that the caller metrics are not created. - args.BetterCAT.Enabled = false - - args.FinalName = webName - args.IsWeb = true - args.Errors = txnErrors - args.Zone = ApdexTolerating - metrics := newMetricTable(100, time.Now()) - CreateTxnMetrics(args, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {webName, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {webRollup, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {dispatcherMetric, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {"WebTransactionTotalTime", "", true, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"WebTransactionTotalTime/zip/zap", "", false, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"Errors/all", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Errors/allWeb", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Errors/" + webName, "", true, []float64{1, 0, 0, 0, 0, 0}}, - {apdexRollup, "", true, []float64{0, 1, 0, 2, 2, 0}}, - {"Apdex/zip/zap", "", false, []float64{0, 1, 0, 2, 2, 0}}, - }) - - args.FinalName = webName - args.IsWeb = true - args.Errors = nil - args.Zone = ApdexTolerating - metrics = newMetricTable(100, time.Now()) - CreateTxnMetrics(args, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {webName, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {webRollup, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {dispatcherMetric, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {"WebTransactionTotalTime", "", true, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"WebTransactionTotalTime/zip/zap", "", false, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {apdexRollup, "", true, []float64{0, 1, 0, 2, 2, 0}}, - {"Apdex/zip/zap", "", false, []float64{0, 1, 0, 2, 2, 0}}, - }) - - args.FinalName = backgroundName - args.IsWeb = false - args.Errors = txnErrors - args.Zone = ApdexNone - metrics = newMetricTable(100, time.Now()) - CreateTxnMetrics(args, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {backgroundName, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {backgroundRollup, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {"OtherTransactionTotalTime", "", true, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"OtherTransactionTotalTime/zip/zap", "", false, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"Errors/all", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Errors/allOther", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Errors/" + backgroundName, "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) - - args.FinalName = backgroundName - args.IsWeb = false - args.Errors = nil - args.Zone = ApdexNone - metrics = newMetricTable(100, time.Now()) - CreateTxnMetrics(args, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {backgroundName, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {backgroundRollup, "", true, []float64{1, 123, 0, 123, 123, 123 * 123}}, - {"OtherTransactionTotalTime", "", true, []float64{1, 150, 150, 150, 150, 150 * 150}}, - {"OtherTransactionTotalTime/zip/zap", "", false, []float64{1, 150, 150, 150, 150, 150 * 150}}, - }) -} - -func TestNewHarvestSetsDefaultValues(t *testing.T) { - now := time.Now() - h := NewHarvest(now, &DfltHarvestCfgr{}) - - if cp := h.TxnEvents.capacity(); cp != MaxTxnEvents { - t.Error("wrong txn event capacity", cp) - } - if cp := h.CustomEvents.capacity(); cp != MaxCustomEvents { - t.Error("wrong custom event capacity", cp) - } - if cp := h.ErrorEvents.capacity(); cp != MaxErrorEvents { - t.Error("wrong error event capacity", cp) - } - if cp := h.SpanEvents.capacity(); cp != MaxSpanEvents { - t.Error("wrong span event capacity", cp) - } -} - -func TestNewHarvestUsesConnectReply(t *testing.T) { - now := time.Now() - h := NewHarvest(now, &DfltHarvestCfgr{ - reportPeriods: map[HarvestTypes]time.Duration{ - HarvestMetricsTraces: FixedHarvestPeriod, - HarvestTypesEvents: time.Second * 5, - }, - maxTxnEvents: &one, - maxCustomEvents: &two, - maxErrorEvents: &three, - maxSpanEvents: &four, - }) - - if cp := h.TxnEvents.capacity(); cp != 1 { - t.Error("wrong txn event capacity", cp) - } - if cp := h.CustomEvents.capacity(); cp != 2 { - t.Error("wrong custom event capacity", cp) - } - if cp := h.ErrorEvents.capacity(); cp != 3 { - t.Error("wrong error event capacity", cp) - } - if cp := h.SpanEvents.capacity(); cp != 4 { - t.Error("wrong span event capacity", cp) - } -} - -func TestConfigurableHarvestZeroHarvestLimits(t *testing.T) { - now := time.Now() - - var zero uint - h := NewHarvest(now, &DfltHarvestCfgr{ - reportPeriods: map[HarvestTypes]time.Duration{ - HarvestMetricsTraces: FixedHarvestPeriod, - HarvestTypesEvents: time.Second * 5, - }, - maxTxnEvents: &zero, - maxCustomEvents: &zero, - maxErrorEvents: &zero, - maxSpanEvents: &zero, - }) - if cp := h.TxnEvents.capacity(); cp != 0 { - t.Error("wrong txn event capacity", cp) - } - if cp := h.CustomEvents.capacity(); cp != 0 { - t.Error("wrong custom event capacity", cp) - } - if cp := h.ErrorEvents.capacity(); cp != 0 { - t.Error("wrong error event capacity", cp) - } - if cp := h.SpanEvents.capacity(); cp != 0 { - t.Error("wrong error event capacity", cp) - } - - // Add events to ensure that adding events to zero-capacity pools is - // safe. - h.TxnEvents.AddTxnEvent(&TxnEvent{}, 1.0) - h.CustomEvents.Add(&CustomEvent{}) - h.ErrorEvents.Add(&ErrorEvent{}, 1.0) - h.SpanEvents.addEventPopulated(&sampleSpanEvent) - - // Create the payloads to ensure doing so with zero-capacity pools is - // safe. - payloads := h.Ready(now.Add(2 * time.Minute)).Payloads(false) - for _, p := range payloads { - js, err := p.Data("agentRunID", now.Add(2*time.Minute)) - if nil != err { - t.Error(err) - continue - } - // Only metric data should be present. - if (p.EndpointMethod() == "metric_data") != - (string(js) != "") { - t.Error(p.EndpointMethod(), string(js)) - } - } -} diff --git a/internal/integrationsupport/integrationsupport.go b/internal/integrationsupport/integrationsupport.go deleted file mode 100644 index f10bebd03..000000000 --- a/internal/integrationsupport/integrationsupport.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package integrationsupport exists to expose functionality to integration -// packages without adding noise to the public API. -package integrationsupport - -import ( - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal" -) - -// AddAgentAttribute allows instrumentation packages to add agent attributes. -func AddAgentAttribute(txn newrelic.Transaction, id internal.AgentAttributeID, stringVal string, otherVal interface{}) { - if aa, ok := txn.(internal.AddAgentAttributer); ok { - aa.AddAgentAttribute(id, stringVal, otherVal) - } -} - -// AddAgentSpanAttribute allows instrumentation packages to add span attributes. -func AddAgentSpanAttribute(txn newrelic.Transaction, key internal.SpanAttribute, val string) { - internal.AddAgentSpanAttribute(txn, key, val) -} - -// This code below is used for testing and is based on the similar code in internal_test.go in -// the newrelic package. That code is not exported, though, and we frequently need something similar -// for integration packages, so it is copied here. -const ( - testLicenseKey = "0123456789012345678901234567890123456789" - SampleAppName = "my app" -) - -// ExpectApp combines Application and Expect, for use in validating data in test apps -type ExpectApp interface { - internal.Expect - newrelic.Application -} - -// NewTestApp creates an ExpectApp with the given ConnectReply function and Config function -func NewTestApp(replyfn func(*internal.ConnectReply), cfgFn func(*newrelic.Config)) ExpectApp { - - cfg := newrelic.NewConfig(SampleAppName, testLicenseKey) - - if nil != cfgFn { - cfgFn(&cfg) - } - - // Prevent spawning app goroutines in tests. - if !cfg.ServerlessMode.Enabled { - cfg.Enabled = false - } - - app, err := newrelic.NewApplication(cfg) - if nil != err { - panic(err) - } - - internal.HarvestTesting(app, replyfn) - - return app.(ExpectApp) -} - -// NewBasicTestApp creates an ExpectApp with the standard testing connect reply function and config -func NewBasicTestApp() ExpectApp { - return NewTestApp(nil, BasicConfigFn) -} - -// BasicConfigFn is a default config function to be used when no special settings are needed for a test app -var BasicConfigFn = func(cfg *newrelic.Config) { - cfg.Enabled = false -} - -// DTEnabledCfgFn is a reusable Config function that sets Distributed Tracing to enabled -var DTEnabledCfgFn = func(cfg *newrelic.Config) { - cfg.Enabled = false - cfg.DistributedTracer.Enabled = true -} - -// SampleEverythingReplyFn is a reusable ConnectReply function that samples everything -var SampleEverythingReplyFn = func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} -} diff --git a/internal/intrinsics.go b/internal/intrinsics.go deleted file mode 100644 index c8a58b8a1..000000000 --- a/internal/intrinsics.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" -) - -func addOptionalStringField(w *jsonFieldsWriter, key, value string) { - if value != "" { - w.stringField(key, value) - } -} - -func intrinsicsJSON(e *TxnEvent, buf *bytes.Buffer) { - w := jsonFieldsWriter{buf: buf} - - buf.WriteByte('{') - - w.floatField("totalTime", e.TotalTime.Seconds()) - - if e.BetterCAT.Enabled { - w.stringField("guid", e.BetterCAT.ID) - w.stringField("traceId", e.BetterCAT.TraceID()) - w.writerField("priority", e.BetterCAT.Priority) - w.boolField("sampled", e.BetterCAT.Sampled) - } - - if e.CrossProcess.Used() { - addOptionalStringField(&w, "client_cross_process_id", e.CrossProcess.ClientID) - addOptionalStringField(&w, "trip_id", e.CrossProcess.TripID) - addOptionalStringField(&w, "path_hash", e.CrossProcess.PathHash) - addOptionalStringField(&w, "referring_transaction_guid", e.CrossProcess.ReferringTxnGUID) - } - - if e.CrossProcess.IsSynthetics() { - addOptionalStringField(&w, "synthetics_resource_id", e.CrossProcess.Synthetics.ResourceID) - addOptionalStringField(&w, "synthetics_job_id", e.CrossProcess.Synthetics.JobID) - addOptionalStringField(&w, "synthetics_monitor_id", e.CrossProcess.Synthetics.MonitorID) - } - - buf.WriteByte('}') -} diff --git a/internal/json_object_writer.go b/internal/json_object_writer.go deleted file mode 100644 index 2a5cc908d..000000000 --- a/internal/json_object_writer.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - - "github.com/newrelic/go-agent/internal/jsonx" -) - -type jsonWriter interface { - WriteJSON(buf *bytes.Buffer) -} - -type jsonFieldsWriter struct { - buf *bytes.Buffer - needsComma bool -} - -func (w *jsonFieldsWriter) addKey(key string) { - if w.needsComma { - w.buf.WriteByte(',') - } else { - w.needsComma = true - } - // defensively assume that the key needs escaping: - jsonx.AppendString(w.buf, key) - w.buf.WriteByte(':') -} - -func (w *jsonFieldsWriter) stringField(key string, val string) { - w.addKey(key) - jsonx.AppendString(w.buf, val) -} - -func (w *jsonFieldsWriter) intField(key string, val int64) { - w.addKey(key) - jsonx.AppendInt(w.buf, val) -} - -func (w *jsonFieldsWriter) floatField(key string, val float64) { - w.addKey(key) - jsonx.AppendFloat(w.buf, val) -} - -func (w *jsonFieldsWriter) boolField(key string, val bool) { - w.addKey(key) - if val { - w.buf.WriteString("true") - } else { - w.buf.WriteString("false") - } -} - -func (w *jsonFieldsWriter) rawField(key string, val JSONString) { - w.addKey(key) - w.buf.WriteString(string(val)) -} - -func (w *jsonFieldsWriter) writerField(key string, val jsonWriter) { - w.addKey(key) - val.WriteJSON(w.buf) -} diff --git a/internal/jsonx/encode.go b/internal/jsonx/encode.go deleted file mode 100644 index 6495829f7..000000000 --- a/internal/jsonx/encode.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2010 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 jsonx extends the encoding/json package to encode JSON -// incrementally and without requiring reflection. -package jsonx - -import ( - "bytes" - "encoding/json" - "math" - "reflect" - "strconv" - "unicode/utf8" -) - -var hex = "0123456789abcdef" - -// AppendString escapes s appends it to buf. -func AppendString(buf *bytes.Buffer, s string) { - buf.WriteByte('"') - start := 0 - for i := 0; i < len(s); { - if b := s[i]; b < utf8.RuneSelf { - if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' { - i++ - continue - } - if start < i { - buf.WriteString(s[start:i]) - } - switch b { - case '\\', '"': - buf.WriteByte('\\') - buf.WriteByte(b) - case '\n': - buf.WriteByte('\\') - buf.WriteByte('n') - case '\r': - buf.WriteByte('\\') - buf.WriteByte('r') - case '\t': - buf.WriteByte('\\') - buf.WriteByte('t') - default: - // This encodes bytes < 0x20 except for \n and \r, - // as well as <, > and &. The latter are escaped because they - // can lead to security holes when user-controlled strings - // are rendered into JSON and served to some browsers. - buf.WriteString(`\u00`) - buf.WriteByte(hex[b>>4]) - buf.WriteByte(hex[b&0xF]) - } - i++ - start = i - continue - } - c, size := utf8.DecodeRuneInString(s[i:]) - if c == utf8.RuneError && size == 1 { - if start < i { - buf.WriteString(s[start:i]) - } - buf.WriteString(`\ufffd`) - i += size - start = i - continue - } - // U+2028 is LINE SEPARATOR. - // U+2029 is PARAGRAPH SEPARATOR. - // They are both technically valid characters in JSON strings, - // but don't work in JSONP, which has to be evaluated as JavaScript, - // and can lead to security holes there. It is valid JSON to - // escape them, so we do so unconditionally. - // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. - if c == '\u2028' || c == '\u2029' { - if start < i { - buf.WriteString(s[start:i]) - } - buf.WriteString(`\u202`) - buf.WriteByte(hex[c&0xF]) - i += size - start = i - continue - } - i += size - } - if start < len(s) { - buf.WriteString(s[start:]) - } - buf.WriteByte('"') -} - -// AppendStringArray appends an array of string literals to buf. -func AppendStringArray(buf *bytes.Buffer, a ...string) { - buf.WriteByte('[') - for i, s := range a { - if i > 0 { - buf.WriteByte(',') - } - AppendString(buf, s) - } - buf.WriteByte(']') -} - -// AppendFloat appends a numeric literal representing the value to buf. -func AppendFloat(buf *bytes.Buffer, x float64) error { - var scratch [64]byte - - if math.IsInf(x, 0) || math.IsNaN(x) { - return &json.UnsupportedValueError{ - Value: reflect.ValueOf(x), - Str: strconv.FormatFloat(x, 'g', -1, 64), - } - } - - buf.Write(strconv.AppendFloat(scratch[:0], x, 'g', -1, 64)) - return nil -} - -// AppendFloatArray appends an array of numeric literals to buf. -func AppendFloatArray(buf *bytes.Buffer, a ...float64) error { - buf.WriteByte('[') - for i, x := range a { - if i > 0 { - buf.WriteByte(',') - } - if err := AppendFloat(buf, x); err != nil { - return err - } - } - buf.WriteByte(']') - return nil -} - -// AppendInt appends a numeric literal representing the value to buf. -func AppendInt(buf *bytes.Buffer, x int64) { - var scratch [64]byte - buf.Write(strconv.AppendInt(scratch[:0], x, 10)) -} - -// AppendIntArray appends an array of numeric literals to buf. -func AppendIntArray(buf *bytes.Buffer, a ...int64) { - var scratch [64]byte - - buf.WriteByte('[') - for i, x := range a { - if i > 0 { - buf.WriteByte(',') - } - buf.Write(strconv.AppendInt(scratch[:0], x, 10)) - } - buf.WriteByte(']') -} - -// AppendUint appends a numeric literal representing the value to buf. -func AppendUint(buf *bytes.Buffer, x uint64) { - var scratch [64]byte - buf.Write(strconv.AppendUint(scratch[:0], x, 10)) -} - -// AppendUintArray appends an array of numeric literals to buf. -func AppendUintArray(buf *bytes.Buffer, a ...uint64) { - var scratch [64]byte - - buf.WriteByte('[') - for i, x := range a { - if i > 0 { - buf.WriteByte(',') - } - buf.Write(strconv.AppendUint(scratch[:0], x, 10)) - } - buf.WriteByte(']') -} diff --git a/internal/jsonx/encode_test.go b/internal/jsonx/encode_test.go deleted file mode 100644 index fed3ab7f7..000000000 --- a/internal/jsonx/encode_test.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package jsonx - -import ( - "bytes" - "math" - "testing" -) - -func TestAppendFloat(t *testing.T) { - buf := &bytes.Buffer{} - - err := AppendFloat(buf, math.NaN()) - if err == nil { - t.Error("AppendFloat(NaN) should return an error") - } - - err = AppendFloat(buf, math.Inf(1)) - if err == nil { - t.Error("AppendFloat(+Inf) should return an error") - } - - err = AppendFloat(buf, math.Inf(-1)) - if err == nil { - t.Error("AppendFloat(-Inf) should return an error") - } -} - -func TestAppendFloats(t *testing.T) { - buf := &bytes.Buffer{} - - AppendFloatArray(buf) - if want, got := "[]", buf.String(); want != got { - t.Errorf("AppendFloatArray(buf)=%q want=%q", got, want) - } - - buf.Reset() - AppendFloatArray(buf, 3.14) - if want, got := "[3.14]", buf.String(); want != got { - t.Errorf("AppendFloatArray(buf)=%q want=%q", got, want) - } - - buf.Reset() - AppendFloatArray(buf, 1, 2) - if want, got := "[1,2]", buf.String(); want != got { - t.Errorf("AppendFloatArray(buf)=%q want=%q", got, want) - } -} - -func TestAppendInt(t *testing.T) { - buf := &bytes.Buffer{} - - AppendInt(buf, 42) - if got := buf.String(); got != "42" { - t.Errorf("AppendUint(42) = %#q want %#q", got, "42") - } - - buf.Reset() - AppendInt(buf, -42) - if got := buf.String(); got != "-42" { - t.Errorf("AppendUint(-42) = %#q want %#q", got, "-42") - } -} - -func TestAppendIntArray(t *testing.T) { - buf := &bytes.Buffer{} - - AppendIntArray(buf) - if want, got := "[]", buf.String(); want != got { - t.Errorf("AppendIntArray(buf)=%q want=%q", got, want) - } - - buf.Reset() - AppendIntArray(buf, 42) - if want, got := "[42]", buf.String(); want != got { - t.Errorf("AppendIntArray(buf)=%q want=%q", got, want) - } - - buf.Reset() - AppendIntArray(buf, 1, -2) - if want, got := "[1,-2]", buf.String(); want != got { - t.Errorf("AppendIntArray(buf)=%q want=%q", got, want) - } - - buf.Reset() - AppendIntArray(buf, 1, -2, 0) - if want, got := "[1,-2,0]", buf.String(); want != got { - t.Errorf("AppendIntArray(buf)=%q want=%q", got, want) - } -} - -func TestAppendUint(t *testing.T) { - buf := &bytes.Buffer{} - - AppendUint(buf, 42) - if got := buf.String(); got != "42" { - t.Errorf("AppendUint(42) = %#q want %#q", got, "42") - } -} - -func TestAppendUintArray(t *testing.T) { - buf := &bytes.Buffer{} - - AppendUintArray(buf) - if want, got := "[]", buf.String(); want != got { - t.Errorf("AppendUintArray(buf)=%q want=%q", got, want) - } - - buf.Reset() - AppendUintArray(buf, 42) - if want, got := "[42]", buf.String(); want != got { - t.Errorf("AppendUintArray(buf)=%q want=%q", got, want) - } - - buf.Reset() - AppendUintArray(buf, 1, 2) - if want, got := "[1,2]", buf.String(); want != got { - t.Errorf("AppendUintArray(buf)=%q want=%q", got, want) - } - - buf.Reset() - AppendUintArray(buf, 1, 2, 3) - if want, got := "[1,2,3]", buf.String(); want != got { - t.Errorf("AppendUintArray(buf)=%q want=%q", got, want) - } -} - -var encodeStringTests = []struct { - in string - out string -}{ - {"\x00", `"\u0000"`}, - {"\x01", `"\u0001"`}, - {"\x02", `"\u0002"`}, - {"\x03", `"\u0003"`}, - {"\x04", `"\u0004"`}, - {"\x05", `"\u0005"`}, - {"\x06", `"\u0006"`}, - {"\x07", `"\u0007"`}, - {"\x08", `"\u0008"`}, - {"\x09", `"\t"`}, - {"\x0a", `"\n"`}, - {"\x0b", `"\u000b"`}, - {"\x0c", `"\u000c"`}, - {"\x0d", `"\r"`}, - {"\x0e", `"\u000e"`}, - {"\x0f", `"\u000f"`}, - {"\x10", `"\u0010"`}, - {"\x11", `"\u0011"`}, - {"\x12", `"\u0012"`}, - {"\x13", `"\u0013"`}, - {"\x14", `"\u0014"`}, - {"\x15", `"\u0015"`}, - {"\x16", `"\u0016"`}, - {"\x17", `"\u0017"`}, - {"\x18", `"\u0018"`}, - {"\x19", `"\u0019"`}, - {"\x1a", `"\u001a"`}, - {"\x1b", `"\u001b"`}, - {"\x1c", `"\u001c"`}, - {"\x1d", `"\u001d"`}, - {"\x1e", `"\u001e"`}, - {"\x1f", `"\u001f"`}, - {"\\", `"\\"`}, - {`"`, `"\""`}, - {"the\u2028quick\t\nbrown\u2029fox", `"the\u2028quick\t\nbrown\u2029fox"`}, -} - -func TestAppendString(t *testing.T) { - buf := &bytes.Buffer{} - - for _, tt := range encodeStringTests { - buf.Reset() - - AppendString(buf, tt.in) - if got := buf.String(); got != tt.out { - t.Errorf("AppendString(%q) = %#q, want %#q", tt.in, got, tt.out) - } - } -} diff --git a/internal/labels.go b/internal/labels.go deleted file mode 100644 index 4c37f7963..000000000 --- a/internal/labels.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import "encoding/json" - -// Labels is used for connect JSON formatting. -type Labels map[string]string - -// MarshalJSON requires a comment for golint? -func (l Labels) MarshalJSON() ([]byte, error) { - ls := make([]struct { - Key string `json:"label_type"` - Value string `json:"label_value"` - }, len(l)) - - i := 0 - for key, val := range l { - ls[i].Key = key - ls[i].Value = val - i++ - } - - return json.Marshal(ls) -} diff --git a/internal/limits.go b/internal/limits.go deleted file mode 100644 index 6ba0e192e..000000000 --- a/internal/limits.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import "time" - -const ( - // app behavior - - // FixedHarvestPeriod is the period that fixed period data (metrics, - // traces, and span events) is sent to New Relic. - FixedHarvestPeriod = 60 * time.Second - // DefaultConfigurableEventHarvestMs is the period for custom, error, - // and transaction events if the connect response's - // "event_harvest_config.report_period_ms" is missing or invalid. - DefaultConfigurableEventHarvestMs = 60 * 1000 - // CollectorTimeout is the timeout used in the client for communication - // with New Relic's servers. - CollectorTimeout = 20 * time.Second - // AppDataChanSize is the size of the channel that contains data sent - // the app processor. - AppDataChanSize = 200 - failedMetricAttemptsLimit = 5 - failedEventsAttemptsLimit = 10 - // maxPayloadSizeInBytes specifies the maximum payload size in bytes that - // should be sent to any endpoint - maxPayloadSizeInBytes = 1000 * 1000 - - // transaction behavior - maxStackTraceFrames = 100 - // MaxTxnErrors is the maximum number of errors captured per - // transaction. - MaxTxnErrors = 5 - maxTxnSlowQueries = 10 - - startingTxnTraceNodes = 16 - maxTxnTraceNodes = 256 - - // harvest data - maxMetrics = 2 * 1000 - // MaxCustomEvents is the maximum number of Transaction Events that can be captured - // per 60-second harvest cycle - MaxCustomEvents = 10 * 1000 - // MaxTxnEvents is the maximum number of Transaction Events that can be captured - // per 60-second harvest cycle - MaxTxnEvents = 10 * 1000 - maxRegularTraces = 1 - maxSyntheticsTraces = 20 - // MaxErrorEvents is the maximum number of Error Events that can be captured - // per 60-second harvest cycle - MaxErrorEvents = 100 - maxHarvestErrors = 20 - maxHarvestSlowSQLs = 10 - // MaxSpanEvents is the maximum number of Span Events that can be captured - // per 60-second harvest cycle - MaxSpanEvents = 1000 - - // attributes - attributeKeyLengthLimit = 255 - attributeValueLengthLimit = 255 - attributeUserLimit = 64 - // AttributeErrorLimit limits the number of extra attributes that can be - // provided when noticing an error. - AttributeErrorLimit = 32 - customEventAttributeLimit = 64 - - // Limits affecting Config validation are found in the config package. - - // RuntimeSamplerPeriod is the period of the runtime sampler. Runtime - // metrics should not depend on the sampler period, but the period must - // be the same across instances. For that reason, this value should not - // be changed without notifying customers that they must update all - // instance simultaneously for valid runtime metrics. - RuntimeSamplerPeriod = 60 * time.Second - - txnNameCacheLimit = 40 -) diff --git a/internal/logger/logger.go b/internal/logger/logger.go deleted file mode 100644 index f5f13212d..000000000 --- a/internal/logger/logger.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package logger - -import ( - "encoding/json" - "fmt" - "io" - "log" - "os" -) - -// Logger matches newrelic.Logger to allow implementations to be passed to -// internal packages. -type Logger interface { - Error(msg string, context map[string]interface{}) - Warn(msg string, context map[string]interface{}) - Info(msg string, context map[string]interface{}) - Debug(msg string, context map[string]interface{}) - DebugEnabled() bool -} - -// ShimLogger implements Logger and does nothing. -type ShimLogger struct { - // IsDebugEnabled is useful as it allows DebugEnabled code paths to be - // tested. - IsDebugEnabled bool -} - -// Error allows ShimLogger to implement Logger. -func (s ShimLogger) Error(string, map[string]interface{}) {} - -// Warn allows ShimLogger to implement Logger. -func (s ShimLogger) Warn(string, map[string]interface{}) {} - -// Info allows ShimLogger to implement Logger. -func (s ShimLogger) Info(string, map[string]interface{}) {} - -// Debug allows ShimLogger to implement Logger. -func (s ShimLogger) Debug(string, map[string]interface{}) {} - -// DebugEnabled allows ShimLogger to implement Logger. -func (s ShimLogger) DebugEnabled() bool { return s.IsDebugEnabled } - -type logFile struct { - l *log.Logger - doDebug bool -} - -// New creates a basic Logger. -func New(w io.Writer, doDebug bool) Logger { - return &logFile{ - l: log.New(w, logPid, logFlags), - doDebug: doDebug, - } -} - -const logFlags = log.Ldate | log.Ltime | log.Lmicroseconds - -var ( - logPid = fmt.Sprintf("(%d) ", os.Getpid()) -) - -func (f *logFile) fire(level, msg string, ctx map[string]interface{}) { - js, err := json.Marshal(struct { - Level string `json:"level"` - Event string `json:"msg"` - Context map[string]interface{} `json:"context"` - }{ - level, - msg, - ctx, - }) - if nil == err { - f.l.Print(string(js)) - } else { - f.l.Printf("unable to marshal log entry: %v", err) - } -} - -func (f *logFile) Error(msg string, ctx map[string]interface{}) { - f.fire("error", msg, ctx) -} -func (f *logFile) Warn(msg string, ctx map[string]interface{}) { - f.fire("warn", msg, ctx) -} -func (f *logFile) Info(msg string, ctx map[string]interface{}) { - f.fire("info", msg, ctx) -} -func (f *logFile) Debug(msg string, ctx map[string]interface{}) { - if f.doDebug { - f.fire("debug", msg, ctx) - } -} -func (f *logFile) DebugEnabled() bool { return f.doDebug } diff --git a/internal/metric_names.go b/internal/metric_names.go deleted file mode 100644 index ce7ce8b2a..000000000 --- a/internal/metric_names.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -const ( - apdexRollup = "Apdex" - apdexPrefix = "Apdex/" - - webRollup = "WebTransaction" - backgroundRollup = "OtherTransaction/all" - - // https://source.datanerd.us/agents/agent-specs/blob/master/Total-Time-Async.md - totalTimeWeb = "WebTransactionTotalTime" - totalTimeBackground = "OtherTransactionTotalTime" - - errorsPrefix = "Errors/" - - // "HttpDispatcher" metric is used for the overview graph, and - // therefore should only be made for web transactions. - dispatcherMetric = "HttpDispatcher" - - queueMetric = "WebFrontend/QueueTime" - - webMetricPrefix = "WebTransaction/Go" - backgroundMetricPrefix = "OtherTransaction/Go" - - instanceReporting = "Instance/Reporting" - - // https://newrelic.atlassian.net/wiki/display/eng/Custom+Events+in+New+Relic+Agents - customEventsSeen = "Supportability/Events/Customer/Seen" - customEventsSent = "Supportability/Events/Customer/Sent" - - // https://source.datanerd.us/agents/agent-specs/blob/master/Transaction-Events-PORTED.md - txnEventsSeen = "Supportability/AnalyticsEvents/TotalEventsSeen" - txnEventsSent = "Supportability/AnalyticsEvents/TotalEventsSent" - - // https://source.datanerd.us/agents/agent-specs/blob/master/Error-Events.md - errorEventsSeen = "Supportability/Events/TransactionError/Seen" - errorEventsSent = "Supportability/Events/TransactionError/Sent" - - // https://source.datanerd.us/agents/agent-specs/blob/master/Span-Events.md - spanEventsSeen = "Supportability/SpanEvent/TotalEventsSeen" - spanEventsSent = "Supportability/SpanEvent/TotalEventsSent" - - supportabilityDropped = "Supportability/MetricsDropped" - - // Runtime/System Metrics - memoryPhysical = "Memory/Physical" - heapObjectsAllocated = "Memory/Heap/AllocatedObjects" - cpuUserUtilization = "CPU/User/Utilization" - cpuSystemUtilization = "CPU/System/Utilization" - cpuUserTime = "CPU/User Time" - cpuSystemTime = "CPU/System Time" - runGoroutine = "Go/Runtime/Goroutines" - gcPauseFraction = "GC/System/Pause Fraction" - gcPauses = "GC/System/Pauses" - - // Distributed Tracing Supportability Metrics - supportTracingAcceptSuccess = "Supportability/DistributedTrace/AcceptPayload/Success" - supportTracingAcceptException = "Supportability/DistributedTrace/AcceptPayload/Exception" - supportTracingAcceptParseException = "Supportability/DistributedTrace/AcceptPayload/ParseException" - supportTracingCreateBeforeAccept = "Supportability/DistributedTrace/AcceptPayload/Ignored/CreateBeforeAccept" - supportTracingIgnoredMultiple = "Supportability/DistributedTrace/AcceptPayload/Ignored/Multiple" - supportTracingIgnoredVersion = "Supportability/DistributedTrace/AcceptPayload/Ignored/MajorVersion" - supportTracingAcceptUntrustedAccount = "Supportability/DistributedTrace/AcceptPayload/Ignored/UntrustedAccount" - supportTracingAcceptNull = "Supportability/DistributedTrace/AcceptPayload/Ignored/Null" - supportTracingCreatePayloadSuccess = "Supportability/DistributedTrace/CreatePayload/Success" - supportTracingCreatePayloadException = "Supportability/DistributedTrace/CreatePayload/Exception" - - // Configurable event harvest supportability metrics - supportReportPeriod = "Supportability/EventHarvest/ReportPeriod" - supportTxnEventLimit = "Supportability/EventHarvest/AnalyticEventData/HarvestLimit" - supportCustomEventLimit = "Supportability/EventHarvest/CustomEventData/HarvestLimit" - supportErrorEventLimit = "Supportability/EventHarvest/ErrorEventData/HarvestLimit" - supportSpanEventLimit = "Supportability/EventHarvest/SpanEventData/HarvestLimit" -) - -// DistributedTracingSupport is used to track distributed tracing activity for -// supportability. -type DistributedTracingSupport struct { - AcceptPayloadSuccess bool // AcceptPayload was called successfully - AcceptPayloadException bool // AcceptPayload had a generic exception - AcceptPayloadParseException bool // AcceptPayload had a parsing exception - AcceptPayloadCreateBeforeAccept bool // AcceptPayload was ignored because CreatePayload had already been called - AcceptPayloadIgnoredMultiple bool // AcceptPayload was ignored because AcceptPayload had already been called - AcceptPayloadIgnoredVersion bool // AcceptPayload was ignored because the payload's major version was greater than the agent's - AcceptPayloadUntrustedAccount bool // AcceptPayload was ignored because the payload was untrusted - AcceptPayloadNullPayload bool // AcceptPayload was ignored because the payload was nil - CreatePayloadSuccess bool // CreatePayload was called successfully - CreatePayloadException bool // CreatePayload had a generic exception -} - -type rollupMetric struct { - all string - allWeb string - allOther string -} - -func newRollupMetric(s string) rollupMetric { - return rollupMetric{ - all: s + "all", - allWeb: s + "allWeb", - allOther: s + "allOther", - } -} - -func (r rollupMetric) webOrOther(isWeb bool) string { - if isWeb { - return r.allWeb - } - return r.allOther -} - -var ( - errorsRollupMetric = newRollupMetric("Errors/") - - // source.datanerd.us/agents/agent-specs/blob/master/APIs/external_segment.md - // source.datanerd.us/agents/agent-specs/blob/master/APIs/external_cat.md - // source.datanerd.us/agents/agent-specs/blob/master/Cross-Application-Tracing-PORTED.md - externalRollupMetric = newRollupMetric("External/") - - // source.datanerd.us/agents/agent-specs/blob/master/Datastore-Metrics-PORTED.md - datastoreRollupMetric = newRollupMetric("Datastore/") - - datastoreProductMetricsCache = map[string]rollupMetric{ - "Cassandra": newRollupMetric("Datastore/Cassandra/"), - "Derby": newRollupMetric("Datastore/Derby/"), - "Elasticsearch": newRollupMetric("Datastore/Elasticsearch/"), - "Firebird": newRollupMetric("Datastore/Firebird/"), - "IBMDB2": newRollupMetric("Datastore/IBMDB2/"), - "Informix": newRollupMetric("Datastore/Informix/"), - "Memcached": newRollupMetric("Datastore/Memcached/"), - "MongoDB": newRollupMetric("Datastore/MongoDB/"), - "MySQL": newRollupMetric("Datastore/MySQL/"), - "MSSQL": newRollupMetric("Datastore/MSSQL/"), - "Oracle": newRollupMetric("Datastore/Oracle/"), - "Postgres": newRollupMetric("Datastore/Postgres/"), - "Redis": newRollupMetric("Datastore/Redis/"), - "Solr": newRollupMetric("Datastore/Solr/"), - "SQLite": newRollupMetric("Datastore/SQLite/"), - "CouchDB": newRollupMetric("Datastore/CouchDB/"), - "Riak": newRollupMetric("Datastore/Riak/"), - "VoltDB": newRollupMetric("Datastore/VoltDB/"), - } -) - -func customSegmentMetric(s string) string { - return "Custom/" + s -} - -// customMetric is used to construct custom metrics from the input given to -// Application.RecordCustomMetric. Note that the "Custom/" prefix helps prevent -// collision with other agent metrics, but does not eliminate the possibility -// since "Custom/" is also used for segments. -func customMetric(customerInput string) string { - return "Custom/" + customerInput -} - -// DatastoreMetricKey contains the fields by which datastore metrics are -// aggregated. -type DatastoreMetricKey struct { - Product string - Collection string - Operation string - Host string - PortPathOrID string -} - -type externalMetricKey struct { - Host string - Library string - Method string - ExternalCrossProcessID string - ExternalTransactionName string -} - -// MessageMetricKey is the key to use for message segments. -type MessageMetricKey struct { - Library string - DestinationType string - Consumer bool - DestinationName string - DestinationTemp bool -} - -// Name returns the metric name value for this MessageMetricKey to be used for -// scoped and unscoped metrics. -// -// Producers -// MessageBroker/{Library}/{Destination Type}/{Action}/Named/{Destination Name} -// MessageBroker/{Library}/{Destination Type}/{Action}/Temp -// -// Consumers -// OtherTransaction/Message/{Library}/{DestinationType}/Named/{Destination Name} -// OtherTransaction/Message/{Library}/{DestinationType}/Temp -func (key MessageMetricKey) Name() string { - var destination string - if key.DestinationTemp { - destination = "Temp" - } else if key.DestinationName == "" { - destination = "Named/Unknown" - } else { - destination = "Named/" + key.DestinationName - } - - if key.Consumer { - return "Message/" + key.Library + - "/" + key.DestinationType + - "/" + destination - } - return "MessageBroker/" + key.Library + - "/" + key.DestinationType + - "/Produce/" + destination -} - -func datastoreScopedMetric(key DatastoreMetricKey) string { - if "" != key.Collection { - return datastoreStatementMetric(key) - } - return datastoreOperationMetric(key) -} - -// Datastore/{datastore}/* -func datastoreProductMetric(key DatastoreMetricKey) rollupMetric { - d, ok := datastoreProductMetricsCache[key.Product] - if ok { - return d - } - return newRollupMetric("Datastore/" + key.Product + "/") -} - -// Datastore/operation/{datastore}/{operation} -func datastoreOperationMetric(key DatastoreMetricKey) string { - return "Datastore/operation/" + key.Product + - "/" + key.Operation -} - -// Datastore/statement/{datastore}/{table}/{operation} -func datastoreStatementMetric(key DatastoreMetricKey) string { - return "Datastore/statement/" + key.Product + - "/" + key.Collection + - "/" + key.Operation -} - -// Datastore/instance/{datastore}/{host}/{port_path_or_id} -func datastoreInstanceMetric(key DatastoreMetricKey) string { - return "Datastore/instance/" + key.Product + - "/" + key.Host + - "/" + key.PortPathOrID -} - -func (key externalMetricKey) scopedMetric() string { - if "" != key.ExternalCrossProcessID && "" != key.ExternalTransactionName { - return externalTransactionMetric(key) - } - - if key.Method == "" { - // External/{host}/{library} - return "External/" + key.Host + "/" + key.Library - } - // External/{host}/{library}/{method} - return "External/" + key.Host + "/" + key.Library + "/" + key.Method -} - -// External/{host}/all -func externalHostMetric(key externalMetricKey) string { - return "External/" + key.Host + "/all" -} - -// ExternalApp/{host}/{external_id}/all -func externalAppMetric(key externalMetricKey) string { - return "ExternalApp/" + key.Host + - "/" + key.ExternalCrossProcessID + "/all" -} - -// ExternalTransaction/{host}/{external_id}/{external_txnname} -func externalTransactionMetric(key externalMetricKey) string { - return "ExternalTransaction/" + key.Host + - "/" + key.ExternalCrossProcessID + - "/" + key.ExternalTransactionName -} - -func callerFields(c payloadCaller) string { - return "/" + c.Type + - "/" + c.Account + - "/" + c.App + - "/" + c.TransportType + - "/" -} - -// DurationByCaller/{type}/{account}/{app}/{transport}/* -func durationByCallerMetric(c payloadCaller) rollupMetric { - return newRollupMetric("DurationByCaller" + callerFields(c)) -} - -// ErrorsByCaller/{type}/{account}/{app}/{transport}/* -func errorsByCallerMetric(c payloadCaller) rollupMetric { - return newRollupMetric("ErrorsByCaller" + callerFields(c)) -} - -// TransportDuration/{type}/{account}/{app}/{transport}/* -func transportDurationMetric(c payloadCaller) rollupMetric { - return newRollupMetric("TransportDuration" + callerFields(c)) -} diff --git a/internal/metric_rules.go b/internal/metric_rules.go deleted file mode 100644 index 46ecc557a..000000000 --- a/internal/metric_rules.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "regexp" - "sort" - "strings" -) - -type ruleResult int - -const ( - ruleMatched ruleResult = iota - ruleUnmatched - ruleIgnore -) - -type metricRule struct { - // 'Ignore' indicates if the entire transaction should be discarded if - // there is a match. This field is only used by "url_rules" and - // "transaction_name_rules", not "metric_name_rules". - Ignore bool `json:"ignore"` - EachSegment bool `json:"each_segment"` - ReplaceAll bool `json:"replace_all"` - Terminate bool `json:"terminate_chain"` - Order int `json:"eval_order"` - OriginalReplacement string `json:"replacement"` - RawExpr string `json:"match_expression"` - - // Go's regexp backreferences use '${1}' instead of the Perlish '\1', so - // we transform the replacement string into the Go syntax and store it - // here. - TransformedReplacement string - re *regexp.Regexp -} - -type metricRules []*metricRule - -// Go's regexp backreferences use `${1}` instead of the Perlish `\1`, so we must -// transform the replacement string. This is non-trivial: `\1` is a -// backreference but `\\1` is not. Rather than count the number of back slashes -// preceding the digit, we simply skip rules with tricky replacements. -var ( - transformReplacementAmbiguous = regexp.MustCompile(`\\\\([0-9]+)`) - transformReplacementRegex = regexp.MustCompile(`\\([0-9]+)`) - transformReplacementReplacement = "$${${1}}" -) - -func (rules *metricRules) UnmarshalJSON(data []byte) (err error) { - var raw []*metricRule - - if err := json.Unmarshal(data, &raw); nil != err { - return err - } - - valid := make(metricRules, 0, len(raw)) - - for _, r := range raw { - re, err := regexp.Compile("(?i)" + r.RawExpr) - if err != nil { - // TODO - // Warn("unable to compile rule", { - // "match_expression": r.RawExpr, - // "error": err.Error(), - // }) - continue - } - - if transformReplacementAmbiguous.MatchString(r.OriginalReplacement) { - // TODO - // Warn("unable to transform replacement", { - // "match_expression": r.RawExpr, - // "replacement": r.OriginalReplacement, - // }) - continue - } - - r.re = re - r.TransformedReplacement = transformReplacementRegex.ReplaceAllString(r.OriginalReplacement, - transformReplacementReplacement) - valid = append(valid, r) - } - - sort.Sort(valid) - - *rules = valid - return nil -} - -func (rules metricRules) Len() int { - return len(rules) -} - -// Rules should be applied in increasing order -func (rules metricRules) Less(i, j int) bool { - return rules[i].Order < rules[j].Order -} -func (rules metricRules) Swap(i, j int) { - rules[i], rules[j] = rules[j], rules[i] -} - -func replaceFirst(re *regexp.Regexp, s string, replacement string) (ruleResult, string) { - // Note that ReplaceAllStringFunc cannot be used here since it does - // not replace $1 placeholders. - loc := re.FindStringIndex(s) - if nil == loc { - return ruleUnmatched, s - } - firstMatch := s[loc[0]:loc[1]] - firstMatchReplaced := re.ReplaceAllString(firstMatch, replacement) - return ruleMatched, s[0:loc[0]] + firstMatchReplaced + s[loc[1]:] -} - -func (r *metricRule) apply(s string) (ruleResult, string) { - // Rules are strange, and there is no spec. - // This code attempts to duplicate the logic of the PHP agent. - // Ambiguity abounds. - - if r.Ignore { - if r.re.MatchString(s) { - return ruleIgnore, "" - } - return ruleUnmatched, s - } - - if r.ReplaceAll { - if r.re.MatchString(s) { - return ruleMatched, r.re.ReplaceAllString(s, r.TransformedReplacement) - } - return ruleUnmatched, s - } else if r.EachSegment { - segments := strings.Split(s, "/") - applied := make([]string, len(segments)) - result := ruleUnmatched - for i, segment := range segments { - var segmentMatched ruleResult - segmentMatched, applied[i] = replaceFirst(r.re, segment, r.TransformedReplacement) - if segmentMatched == ruleMatched { - result = ruleMatched - } - } - return result, strings.Join(applied, "/") - } else { - return replaceFirst(r.re, s, r.TransformedReplacement) - } -} - -func (rules metricRules) Apply(input string) string { - var res ruleResult - s := input - - for _, rule := range rules { - res, s = rule.apply(s) - - if ruleIgnore == res { - return "" - } - if (ruleMatched == res) && rule.Terminate { - break - } - } - - return s -} diff --git a/internal/metric_rules_test.go b/internal/metric_rules_test.go deleted file mode 100644 index 841a836e1..000000000 --- a/internal/metric_rules_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestMetricRules(t *testing.T) { - var tcs []struct { - Testname string `json:"testname"` - Rules metricRules `json:"rules"` - Tests []struct { - Input string `json:"input"` - Expected string `json:"expected"` - } `json:"tests"` - } - - err := crossagent.ReadJSON("rules.json", &tcs) - if err != nil { - t.Fatal(err) - } - - for _, tc := range tcs { - // This test relies upon Perl-specific regex syntax (negative - // lookahead assertions) which are not implemented in Go's - // regexp package. We believe these types of rules are - // exceedingly rare in practice, so we're skipping - // implementation of this exotic syntax for now. - if tc.Testname == "saxon's test" { - continue - } - - for _, x := range tc.Tests { - out := tc.Rules.Apply(x.Input) - if out != x.Expected { - t.Fatal(tc.Testname, x.Input, out, x.Expected) - } - } - } -} - -func TestMetricRuleWithNegativeLookaheadAssertion(t *testing.T) { - js := `[{ - "match_expression":"^(?!account|application).*", - "replacement":"*", - "ignore":false, - "eval_order":0, - "each_segment":true - }]` - var rules metricRules - err := json.Unmarshal([]byte(js), &rules) - if nil != err { - t.Fatal(err) - } - if 0 != rules.Len() { - t.Fatal(rules) - } -} - -func TestNilApplyRules(t *testing.T) { - var rules metricRules - - input := "hello" - out := rules.Apply(input) - if input != out { - t.Fatal(input, out) - } -} - -func TestAmbiguousReplacement(t *testing.T) { - js := `[{ - "match_expression":"(.*)/[^/]*.(bmp|css|gif|ico|jpg|jpeg|js|png)", - "replacement":"\\\\1/*.\\2", - "ignore":false, - "eval_order":0 - }]` - var rules metricRules - err := json.Unmarshal([]byte(js), &rules) - if nil != err { - t.Fatal(err) - } - if 0 != rules.Len() { - t.Fatal(rules) - } -} - -func TestBadMetricRulesJSON(t *testing.T) { - js := `{}` - var rules metricRules - err := json.Unmarshal([]byte(js), &rules) - if nil == err { - t.Fatal("missing bad json error") - } -} diff --git a/internal/metrics.go b/internal/metrics.go deleted file mode 100644 index 24c10a700..000000000 --- a/internal/metrics.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "time" - - "github.com/newrelic/go-agent/internal/jsonx" -) - -type metricForce int - -const ( - forced metricForce = iota - unforced -) - -type metricID struct { - Name string `json:"name"` - Scope string `json:"scope,omitempty"` -} - -type metricData struct { - // These values are in the units expected by the collector. - countSatisfied float64 // Seconds, or count for Apdex - totalTolerated float64 // Seconds, or count for Apdex - exclusiveFailed float64 // Seconds, or count for Apdex - min float64 // Seconds - max float64 // Seconds - sumSquares float64 // Seconds**2, or 0 for Apdex -} - -func metricDataFromDuration(duration, exclusive time.Duration) metricData { - ds := duration.Seconds() - return metricData{ - countSatisfied: 1, - totalTolerated: ds, - exclusiveFailed: exclusive.Seconds(), - min: ds, - max: ds, - sumSquares: ds * ds, - } -} - -type metric struct { - forced metricForce - data metricData -} - -type metricTable struct { - metricPeriodStart time.Time - failedHarvests int - maxTableSize int // After this max is reached, only forced metrics are added - metrics map[metricID]*metric -} - -func newMetricTable(maxTableSize int, now time.Time) *metricTable { - return &metricTable{ - metricPeriodStart: now, - metrics: make(map[metricID]*metric), - maxTableSize: maxTableSize, - failedHarvests: 0, - } -} - -func (mt *metricTable) full() bool { - return len(mt.metrics) >= mt.maxTableSize -} - -func (data *metricData) aggregate(src metricData) { - data.countSatisfied += src.countSatisfied - data.totalTolerated += src.totalTolerated - data.exclusiveFailed += src.exclusiveFailed - - if src.min < data.min { - data.min = src.min - } - if src.max > data.max { - data.max = src.max - } - - data.sumSquares += src.sumSquares -} - -func (mt *metricTable) mergeMetric(id metricID, m metric) { - if to := mt.metrics[id]; nil != to { - to.data.aggregate(m.data) - return - } - - if mt.full() && (unforced == m.forced) { - mt.addSingleCount(supportabilityDropped, forced) - return - } - // NOTE: `new` is used in place of `&m` since the latter will make `m` - // get heap allocated regardless of whether or not this line gets - // reached (running go version go1.5 darwin/amd64). See - // BenchmarkAddingSameMetrics. - alloc := new(metric) - *alloc = m - mt.metrics[id] = alloc -} - -func (mt *metricTable) mergeFailed(from *metricTable) { - fails := from.failedHarvests + 1 - if fails >= failedMetricAttemptsLimit { - return - } - if from.metricPeriodStart.Before(mt.metricPeriodStart) { - mt.metricPeriodStart = from.metricPeriodStart - } - mt.failedHarvests = fails - mt.merge(from, "") -} - -func (mt *metricTable) merge(from *metricTable, newScope string) { - if "" == newScope { - for id, m := range from.metrics { - mt.mergeMetric(id, *m) - } - } else { - for id, m := range from.metrics { - mt.mergeMetric(metricID{Name: id.Name, Scope: newScope}, *m) - } - } -} - -func (mt *metricTable) add(name, scope string, data metricData, force metricForce) { - mt.mergeMetric(metricID{Name: name, Scope: scope}, metric{data: data, forced: force}) -} - -func (mt *metricTable) addCount(name string, count float64, force metricForce) { - mt.add(name, "", metricData{countSatisfied: count}, force) -} - -func (mt *metricTable) addSingleCount(name string, force metricForce) { - mt.addCount(name, float64(1), force) -} - -func (mt *metricTable) addDuration(name, scope string, duration, exclusive time.Duration, force metricForce) { - mt.add(name, scope, metricDataFromDuration(duration, exclusive), force) -} - -func (mt *metricTable) addValueExclusive(name, scope string, total, exclusive float64, force metricForce) { - data := metricData{ - countSatisfied: 1, - totalTolerated: total, - exclusiveFailed: exclusive, - min: total, - max: total, - sumSquares: total * total, - } - mt.add(name, scope, data, force) -} - -func (mt *metricTable) addValue(name, scope string, total float64, force metricForce) { - mt.addValueExclusive(name, scope, total, total, force) -} - -func (mt *metricTable) addApdex(name, scope string, apdexThreshold time.Duration, zone ApdexZone, force metricForce) { - apdexSeconds := apdexThreshold.Seconds() - data := metricData{min: apdexSeconds, max: apdexSeconds} - - switch zone { - case ApdexSatisfying: - data.countSatisfied = 1 - case ApdexTolerating: - data.totalTolerated = 1 - case ApdexFailing: - data.exclusiveFailed = 1 - } - - mt.add(name, scope, data, force) -} - -func (mt *metricTable) CollectorJSON(agentRunID string, now time.Time) ([]byte, error) { - if 0 == len(mt.metrics) { - return nil, nil - } - estimatedBytesPerMetric := 128 - estimatedLen := len(mt.metrics) * estimatedBytesPerMetric - buf := bytes.NewBuffer(make([]byte, 0, estimatedLen)) - buf.WriteByte('[') - - jsonx.AppendString(buf, agentRunID) - buf.WriteByte(',') - jsonx.AppendInt(buf, mt.metricPeriodStart.Unix()) - buf.WriteByte(',') - jsonx.AppendInt(buf, now.Unix()) - buf.WriteByte(',') - - buf.WriteByte('[') - first := true - for id, metric := range mt.metrics { - if first { - first = false - } else { - buf.WriteByte(',') - } - buf.WriteByte('[') - buf.WriteByte('{') - buf.WriteString(`"name":`) - jsonx.AppendString(buf, id.Name) - if id.Scope != "" { - buf.WriteString(`,"scope":`) - jsonx.AppendString(buf, id.Scope) - } - buf.WriteByte('}') - buf.WriteByte(',') - - jsonx.AppendFloatArray(buf, - metric.data.countSatisfied, - metric.data.totalTolerated, - metric.data.exclusiveFailed, - metric.data.min, - metric.data.max, - metric.data.sumSquares) - - buf.WriteByte(']') - } - buf.WriteByte(']') - - buf.WriteByte(']') - return buf.Bytes(), nil -} - -func (mt *metricTable) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { - return mt.CollectorJSON(agentRunID, harvestStart) -} -func (mt *metricTable) MergeIntoHarvest(h *Harvest) { - h.Metrics.mergeFailed(mt) -} - -func (mt *metricTable) ApplyRules(rules metricRules) *metricTable { - if nil == rules { - return mt - } - if len(rules) == 0 { - return mt - } - - applied := newMetricTable(mt.maxTableSize, mt.metricPeriodStart) - cache := make(map[string]string) - - for id, m := range mt.metrics { - out, ok := cache[id.Name] - if !ok { - out = rules.Apply(id.Name) - cache[id.Name] = out - } - - if "" != out { - applied.mergeMetric(metricID{Name: out, Scope: id.Scope}, *m) - } - } - - return applied -} - -func (mt *metricTable) EndpointMethod() string { - return cmdMetrics -} diff --git a/internal/metrics_test.go b/internal/metrics_test.go deleted file mode 100644 index 47b356f1d..000000000 --- a/internal/metrics_test.go +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "fmt" - "testing" - "time" -) - -var ( - start = time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - end = time.Date(2014, time.November, 28, 1, 2, 0, 0, time.UTC) -) - -func TestEmptyMetrics(t *testing.T) { - mt := newMetricTable(20, start) - js, err := mt.CollectorJSON(`12345`, end) - if nil != err { - t.Fatal(err) - } - if nil != js { - t.Error(string(js)) - } -} - -func isValidJSON(data []byte) error { - var v interface{} - - return json.Unmarshal(data, &v) -} - -func TestMetrics(t *testing.T) { - mt := newMetricTable(20, start) - - mt.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("two", "my_scope", 4*time.Second, 2*time.Second, unforced) - mt.addDuration("one", "my_scope", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - - mt.addApdex("apdex satisfied", "", 9*time.Second, ApdexSatisfying, unforced) - mt.addApdex("apdex satisfied", "", 8*time.Second, ApdexSatisfying, unforced) - mt.addApdex("apdex tolerated", "", 7*time.Second, ApdexTolerating, unforced) - mt.addApdex("apdex tolerated", "", 8*time.Second, ApdexTolerating, unforced) - mt.addApdex("apdex failed", "my_scope", 1*time.Second, ApdexFailing, unforced) - - mt.addCount("count 123", float64(123), unforced) - mt.addSingleCount("count 1", unforced) - - ExpectMetrics(t, mt, []WantMetric{ - {"apdex satisfied", "", false, []float64{2, 0, 0, 8, 9, 0}}, - {"apdex tolerated", "", false, []float64{0, 2, 0, 7, 8, 0}}, - {"one", "", false, []float64{2, 4, 2, 2, 2, 8}}, - {"apdex failed", "my_scope", false, []float64{0, 0, 1, 1, 1, 0}}, - {"one", "my_scope", false, []float64{1, 2, 1, 2, 2, 4}}, - {"two", "my_scope", false, []float64{1, 4, 2, 4, 4, 16}}, - {"count 123", "", false, []float64{123, 0, 0, 0, 0, 0}}, - {"count 1", "", false, []float64{1, 0, 0, 0, 0, 0}}, - }) - - js, err := mt.Data("12345", end) - if nil != err { - t.Error(err) - } - // The JSON metric order is not deterministic, so we merely test that it - // is valid JSON. - if err := isValidJSON(js); nil != err { - t.Error(err, string(js)) - } -} - -func TestApplyRules(t *testing.T) { - js := `[ - { - "ignore":false, - "each_segment":false, - "terminate_chain":true, - "replacement":"been_renamed", - "replace_all":false, - "match_expression":"one$", - "eval_order":1 - }, - { - "ignore":true, - "each_segment":false, - "terminate_chain":true, - "replace_all":false, - "match_expression":"ignore_me", - "eval_order":1 - }, - { - "ignore":false, - "each_segment":false, - "terminate_chain":true, - "replacement":"merge_me", - "replace_all":false, - "match_expression":"merge_me[0-9]+$", - "eval_order":1 - } - ]` - var rules metricRules - err := json.Unmarshal([]byte(js), &rules) - if nil != err { - t.Fatal(err) - } - - mt := newMetricTable(20, start) - mt.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("one", "scope1", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("one", "scope2", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("ignore_me", "", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("ignore_me", "scope1", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("ignore_me", "scope2", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("merge_me1", "", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("merge_me2", "", 2*time.Second, 1*time.Second, unforced) - - applied := mt.ApplyRules(rules) - ExpectMetrics(t, applied, []WantMetric{ - {"been_renamed", "", false, []float64{1, 2, 1, 2, 2, 4}}, - {"been_renamed", "scope1", false, []float64{1, 2, 1, 2, 2, 4}}, - {"been_renamed", "scope2", false, []float64{1, 2, 1, 2, 2, 4}}, - {"merge_me", "", false, []float64{2, 4, 2, 2, 2, 8}}, - }) -} - -func TestApplyEmptyRules(t *testing.T) { - js := `[]` - var rules metricRules - err := json.Unmarshal([]byte(js), &rules) - if nil != err { - t.Fatal(err) - } - mt := newMetricTable(20, start) - mt.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("one", "my_scope", 2*time.Second, 1*time.Second, unforced) - applied := mt.ApplyRules(rules) - ExpectMetrics(t, applied, []WantMetric{ - {"one", "", false, []float64{1, 2, 1, 2, 2, 4}}, - {"one", "my_scope", false, []float64{1, 2, 1, 2, 2, 4}}, - }) -} - -func TestApplyNilRules(t *testing.T) { - var rules metricRules - - mt := newMetricTable(20, start) - mt.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - mt.addDuration("one", "my_scope", 2*time.Second, 1*time.Second, unforced) - applied := mt.ApplyRules(rules) - ExpectMetrics(t, applied, []WantMetric{ - {"one", "", false, []float64{1, 2, 1, 2, 2, 4}}, - {"one", "my_scope", false, []float64{1, 2, 1, 2, 2, 4}}, - }) -} - -func TestForced(t *testing.T) { - mt := newMetricTable(0, start) - - mt.addDuration("unforced", "", 1*time.Second, 1*time.Second, unforced) - mt.addDuration("forced", "", 2*time.Second, 2*time.Second, forced) - - ExpectMetrics(t, mt, []WantMetric{ - {"forced", "", true, []float64{1, 2, 2, 2, 2, 4}}, - {supportabilityDropped, "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) - -} - -func TestMetricsMergeIntoEmpty(t *testing.T) { - src := newMetricTable(20, start) - src.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - src.addDuration("two", "", 2*time.Second, 1*time.Second, unforced) - dest := newMetricTable(20, start) - dest.merge(src, "") - - ExpectMetrics(t, dest, []WantMetric{ - {"one", "", false, []float64{1, 2, 1, 2, 2, 4}}, - {"two", "", false, []float64{1, 2, 1, 2, 2, 4}}, - }) -} - -func TestMetricsMergeFromEmpty(t *testing.T) { - src := newMetricTable(20, start) - dest := newMetricTable(20, start) - dest.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - dest.addDuration("two", "", 2*time.Second, 1*time.Second, unforced) - dest.merge(src, "") - - ExpectMetrics(t, dest, []WantMetric{ - {"one", "", false, []float64{1, 2, 1, 2, 2, 4}}, - {"two", "", false, []float64{1, 2, 1, 2, 2, 4}}, - }) -} - -func TestMetricsMerge(t *testing.T) { - src := newMetricTable(20, start) - dest := newMetricTable(20, start) - dest.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - dest.addDuration("two", "", 2*time.Second, 1*time.Second, unforced) - src.addDuration("two", "", 2*time.Second, 1*time.Second, unforced) - src.addDuration("three", "", 2*time.Second, 1*time.Second, unforced) - - dest.merge(src, "") - - ExpectMetrics(t, dest, []WantMetric{ - {"one", "", false, []float64{1, 2, 1, 2, 2, 4}}, - {"two", "", false, []float64{2, 4, 2, 2, 2, 8}}, - {"three", "", false, []float64{1, 2, 1, 2, 2, 4}}, - }) -} - -func TestMergeFailedSuccess(t *testing.T) { - src := newMetricTable(20, start) - dest := newMetricTable(20, end) - dest.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - dest.addDuration("two", "", 2*time.Second, 1*time.Second, unforced) - src.addDuration("two", "", 2*time.Second, 1*time.Second, unforced) - src.addDuration("three", "", 2*time.Second, 1*time.Second, unforced) - - if 0 != dest.failedHarvests { - t.Fatal(dest.failedHarvests) - } - - dest.mergeFailed(src) - - ExpectMetrics(t, dest, []WantMetric{ - {"one", "", false, []float64{1, 2, 1, 2, 2, 4}}, - {"two", "", false, []float64{2, 4, 2, 2, 2, 8}}, - {"three", "", false, []float64{1, 2, 1, 2, 2, 4}}, - }) -} - -func TestMergeFailedLimitReached(t *testing.T) { - src := newMetricTable(20, start) - dest := newMetricTable(20, end) - dest.addDuration("one", "", 2*time.Second, 1*time.Second, unforced) - dest.addDuration("two", "", 2*time.Second, 1*time.Second, unforced) - src.addDuration("two", "", 2*time.Second, 1*time.Second, unforced) - src.addDuration("three", "", 2*time.Second, 1*time.Second, unforced) - - src.failedHarvests = failedMetricAttemptsLimit - - dest.mergeFailed(src) - - ExpectMetrics(t, dest, []WantMetric{ - {"one", "", false, []float64{1, 2, 1, 2, 2, 4}}, - {"two", "", false, []float64{1, 2, 1, 2, 2, 4}}, - }) -} - -func BenchmarkMetricTableCollectorJSON(b *testing.B) { - mt := newMetricTable(2000, time.Now()) - md := metricData{ - countSatisfied: 1234567812345678.1234567812345678, - totalTolerated: 1234567812345678.1234567812345678, - exclusiveFailed: 1234567812345678.1234567812345678, - min: 1234567812345678.1234567812345678, - max: 1234567812345678.1234567812345678, - sumSquares: 1234567812345678.1234567812345678, - } - - for i := 0; i < 20; i++ { - scope := fmt.Sprintf("WebTransaction/Uri/myblog2/%d", i) - - for j := 0; j < 20; j++ { - name := fmt.Sprintf("Datastore/statement/MySQL/City%d/insert", j) - mt.add(name, "", md, forced) - mt.add(name, scope, md, forced) - - name = fmt.Sprintf("WebTransaction/Uri/myblog2/newPost_rum_%d.php", j) - mt.add(name, "", md, forced) - mt.add(name, scope, md, forced) - } - } - - data, err := mt.CollectorJSON("12345", time.Now()) - if nil != err { - b.Fatal(err) - } - if err := isValidJSON(data); nil != err { - b.Fatal(err, string(data)) - } - - b.ResetTimer() - b.ReportAllocs() - - id := "12345" - now := time.Now() - for i := 0; i < b.N; i++ { - mt.CollectorJSON(id, now) - } -} - -func BenchmarkAddingSameMetrics(b *testing.B) { - name := "my_name" - scope := "my_scope" - duration := 2 * time.Second - exclusive := 1 * time.Second - - mt := newMetricTable(2000, time.Now()) - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - mt.addDuration(name, scope, duration, exclusive, forced) - mt.addSingleCount(name, forced) - } -} - -func TestMergedMetricsAreCopied(t *testing.T) { - src := newMetricTable(20, start) - dest := newMetricTable(20, start) - - src.addSingleCount("zip", unforced) - dest.merge(src, "") - src.addSingleCount("zip", unforced) - ExpectMetrics(t, dest, []WantMetric{ - {"zip", "", false, []float64{1, 0, 0, 0, 0, 0}}, - }) -} - -func TestMergedWithScope(t *testing.T) { - src := newMetricTable(20, start) - dest := newMetricTable(20, start) - - src.addSingleCount("one", unforced) - src.addDuration("two", "", 2*time.Second, 1*time.Second, unforced) - dest.addDuration("two", "my_scope", 2*time.Second, 1*time.Second, unforced) - dest.merge(src, "my_scope") - - ExpectMetrics(t, dest, []WantMetric{ - {"one", "my_scope", false, []float64{1, 0, 0, 0, 0, 0}}, - {"two", "my_scope", false, []float64{2, 4, 2, 2, 2, 8}}, - }) -} diff --git a/internal/obfuscate.go b/internal/obfuscate.go deleted file mode 100644 index 7ea3455c5..000000000 --- a/internal/obfuscate.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/base64" - "errors" -) - -// Deobfuscate deobfuscates a byte array. -func Deobfuscate(in string, key []byte) ([]byte, error) { - if len(key) == 0 { - return nil, errors.New("key cannot be zero length") - } - - decoded, err := base64.StdEncoding.DecodeString(in) - if err != nil { - return nil, err - } - - out := make([]byte, len(decoded)) - for i, c := range decoded { - out[i] = c ^ key[i%len(key)] - } - - return out, nil -} - -// Obfuscate obfuscates a byte array for transmission in CAT and RUM. -func Obfuscate(in, key []byte) (string, error) { - if len(key) == 0 { - return "", errors.New("key cannot be zero length") - } - - out := make([]byte, len(in)) - for i, c := range in { - out[i] = c ^ key[i%len(key)] - } - - return base64.StdEncoding.EncodeToString(out), nil -} diff --git a/internal/obfuscate_test.go b/internal/obfuscate_test.go deleted file mode 100644 index a7599bb60..000000000 --- a/internal/obfuscate_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "testing" -) - -func TestDeobfuscate(t *testing.T) { - var out []byte - var err error - - for _, in := range []string{"", "foo"} { - out, err = Deobfuscate(in, []byte("")) - if err == nil { - t.Error("error is nil for an empty key") - } - if out != nil { - t.Errorf("out is not nil; got: %s", out) - } - } - - for _, in := range []string{"invalid_base64", "=moreinvalidbase64", "xx"} { - out, err = Deobfuscate(in, []byte("")) - if err == nil { - t.Error("error is nil for invalid base64") - } - if out != nil { - t.Errorf("out is not nil; got: %s", out) - } - } - - for _, test := range []struct { - input string - key string - expected string - }{ - {"", "BLAHHHH", ""}, - {"NikyPBs8OisiJg==", "BLAHHHH", "testString"}, - } { - out, err = Deobfuscate(test.input, []byte(test.key)) - if err != nil { - t.Errorf("error expected to be nil; got: %v", err) - } - if string(out) != test.expected { - t.Errorf("output mismatch; expected: %s; got: %s", test.expected, out) - } - } -} - -func TestObfuscate(t *testing.T) { - var out string - var err error - - for _, in := range []string{"", "foo"} { - out, err = Obfuscate([]byte(in), []byte("")) - if err == nil { - t.Error("error is nil for an empty key") - } - if out != "" { - t.Errorf("out is not an empty string; got: %s", out) - } - } - - for _, test := range []struct { - input string - key string - expected string - }{ - {"", "BLAHHHH", ""}, - {"testString", "BLAHHHH", "NikyPBs8OisiJg=="}, - } { - out, err = Obfuscate([]byte(test.input), []byte(test.key)) - if err != nil { - t.Errorf("error expected to be nil; got: %v", err) - } - if out != test.expected { - t.Errorf("output mismatch; expected: %s; got: %s", test.expected, out) - } - } -} diff --git a/internal/priority.go b/internal/priority.go deleted file mode 100644 index d785ff3bb..000000000 --- a/internal/priority.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -// Priority allows for a priority sampling of events. When an event -// is created it is given a Priority. Whenever an event pool is -// full and events need to be dropped, the events with the lowest priority -// are dropped. -type Priority float32 - -// According to spec, Agents SHOULD truncate the value to at most 6 -// digits past the decimal point. -const ( - priorityFormat = "%.6f" -) - -// NewPriority returns a new priority. -func NewPriority() Priority { - return Priority(RandFloat32()) -} - -// Float32 returns the priority as a float32. -func (p Priority) Float32() float32 { - return float32(p) -} - -func (p Priority) isLowerPriority(y Priority) bool { - return p < y -} diff --git a/internal/priority_test.go b/internal/priority_test.go deleted file mode 100644 index bb9309d94..000000000 --- a/internal/priority_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "testing" -) - -func TestIsLowerPriority(t *testing.T) { - low := Priority(0.0) - middle := Priority(0.1) - high := Priority(0.999999) - - if !low.isLowerPriority(middle) { - t.Error(low, middle) - } - - if high.isLowerPriority(middle) { - t.Error(high, middle) - } - - if high.isLowerPriority(high) { - t.Error(high, high) - } -} diff --git a/internal/queuing.go b/internal/queuing.go deleted file mode 100644 index 4a09e56d4..000000000 --- a/internal/queuing.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "net/http" - "strconv" - "strings" - "time" -) - -const ( - xRequestStart = "X-Request-Start" - xQueueStart = "X-Queue-Start" -) - -var ( - earliestAcceptableSeconds = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).Unix() - latestAcceptableSeconds = time.Date(2050, time.January, 1, 0, 0, 0, 0, time.UTC).Unix() -) - -func checkQueueTimeSeconds(secondsFloat float64) time.Time { - seconds := int64(secondsFloat) - nanos := int64((secondsFloat - float64(seconds)) * (1000.0 * 1000.0 * 1000.0)) - if seconds > earliestAcceptableSeconds && seconds < latestAcceptableSeconds { - return time.Unix(seconds, nanos) - } - return time.Time{} -} - -func parseQueueTime(s string) time.Time { - f, err := strconv.ParseFloat(s, 64) - if nil != err { - return time.Time{} - } - if f <= 0 { - return time.Time{} - } - - // try microseconds - if t := checkQueueTimeSeconds(f / (1000.0 * 1000.0)); !t.IsZero() { - return t - } - // try milliseconds - if t := checkQueueTimeSeconds(f / (1000.0)); !t.IsZero() { - return t - } - // try seconds - if t := checkQueueTimeSeconds(f); !t.IsZero() { - return t - } - return time.Time{} -} - -// QueueDuration TODO -func QueueDuration(hdr http.Header, txnStart time.Time) time.Duration { - s := hdr.Get(xQueueStart) - if "" == s { - s = hdr.Get(xRequestStart) - } - if "" == s { - return 0 - } - - s = strings.TrimPrefix(s, "t=") - qt := parseQueueTime(s) - if qt.IsZero() { - return 0 - } - if qt.After(txnStart) { - return 0 - } - return txnStart.Sub(qt) -} diff --git a/internal/queuing_test.go b/internal/queuing_test.go deleted file mode 100644 index a1d136b5b..000000000 --- a/internal/queuing_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "net/http" - "testing" - "time" -) - -func TestParseQueueTime(t *testing.T) { - badInput := []string{ - "", - "nope", - "t", - "0", - "0.0", - "9999999999999999999999999999999999999999999999999", - "-1368811467146000", - "3000000000", - "3000000000000", - "900000000", - "900000000000", - } - for _, s := range badInput { - if qt := parseQueueTime(s); !qt.IsZero() { - t.Error(s, qt) - } - } - - testcases := []struct { - input string - expect int64 - }{ - // Microseconds - {"1368811467146000", 1368811467}, - // Milliseconds - {"1368811467146.000", 1368811467}, - {"1368811467146", 1368811467}, - // Seconds - {"1368811467.146000", 1368811467}, - {"1368811467.146", 1368811467}, - {"1368811467", 1368811467}, - } - for _, tc := range testcases { - qt := parseQueueTime(tc.input) - if qt.Unix() != tc.expect { - t.Error(tc.input, tc.expect, qt, qt.UnixNano()) - } - } -} - -func TestQueueDuration(t *testing.T) { - hdr := make(http.Header) - hdr.Set("X-Queue-Start", "1465798814") - qd := QueueDuration(hdr, time.Unix(1465798816, 0)) - if qd != 2*time.Second { - t.Error(qd) - } - - hdr = make(http.Header) - hdr.Set("X-Request-Start", "1465798814") - qd = QueueDuration(hdr, time.Unix(1465798816, 0)) - if qd != 2*time.Second { - t.Error(qd) - } - - hdr = make(http.Header) - qd = QueueDuration(hdr, time.Unix(1465798816, 0)) - if qd != 0 { - t.Error(qd) - } - - hdr = make(http.Header) - hdr.Set("X-Request-Start", "invalid-time") - qd = QueueDuration(hdr, time.Unix(1465798816, 0)) - if qd != 0 { - t.Error(qd) - } - - hdr = make(http.Header) - hdr.Set("X-Queue-Start", "t=1465798814") - qd = QueueDuration(hdr, time.Unix(1465798816, 0)) - if qd != 2*time.Second { - t.Error(qd) - } - - // incorrect time order - hdr = make(http.Header) - hdr.Set("X-Queue-Start", "t=1465798816") - qd = QueueDuration(hdr, time.Unix(1465798814, 0)) - if qd != 0 { - t.Error(qd) - } -} diff --git a/internal/rand.go b/internal/rand.go deleted file mode 100644 index 94c0447d7..000000000 --- a/internal/rand.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "math/rand" - "sync" - "time" -) - -var ( - seededRand = struct { - sync.Mutex - *rand.Rand - }{ - Rand: rand.New(rand.NewSource(int64(time.Now().UnixNano()))), - } -) - -// RandUint64 returns a random uint64. -// -// IMPORTANT! The default rand package functions are not used, since we want to -// minimize the chance that different Go processes duplicate the same -// transaction id. (Note that the rand top level functions "use a default -// shared Source that produces a deterministic sequence of values each time a -// program is run" (and we don't seed the shared Source to avoid changing -// customer apps' behavior)). -func RandUint64() uint64 { - seededRand.Lock() - defer seededRand.Unlock() - - u1 := seededRand.Uint32() - u2 := seededRand.Uint32() - return (uint64(u1) << 32) | uint64(u2) -} - -// RandUint32 returns a random uint32. -func RandUint32() uint32 { - seededRand.Lock() - defer seededRand.Unlock() - - return seededRand.Uint32() -} - -// RandFloat32 returns a random float32 between 0.0 and 1.0. -func RandFloat32() float32 { - seededRand.Lock() - defer seededRand.Unlock() - - for { - if r := seededRand.Float32(); 0.0 != r { - return r - } - } -} - -// RandUint64N returns a random int64 that's -// between 0 and the passed in max, non-inclusive -func RandUint64N(max uint64) uint64 { - return RandUint64() % max -} diff --git a/internal/rules_cache.go b/internal/rules_cache.go deleted file mode 100644 index 31a5a0cf9..000000000 --- a/internal/rules_cache.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import "sync" - -// rulesCache is designed to avoid applying url-rules, txn-name-rules, and -// segment-rules since regexes are expensive! -type rulesCache struct { - sync.RWMutex - cache map[rulesCacheKey]string - maxCacheSize int -} - -type rulesCacheKey struct { - isWeb bool - inputName string -} - -func newRulesCache(maxCacheSize int) *rulesCache { - return &rulesCache{ - cache: make(map[rulesCacheKey]string, maxCacheSize), - maxCacheSize: maxCacheSize, - } -} - -func (cache *rulesCache) find(inputName string, isWeb bool) string { - if nil == cache { - return "" - } - cache.RLock() - defer cache.RUnlock() - - return cache.cache[rulesCacheKey{ - inputName: inputName, - isWeb: isWeb, - }] -} - -func (cache *rulesCache) set(inputName string, isWeb bool, finalName string) { - if nil == cache { - return - } - cache.Lock() - defer cache.Unlock() - - if len(cache.cache) >= cache.maxCacheSize { - return - } - cache.cache[rulesCacheKey{ - inputName: inputName, - isWeb: isWeb, - }] = finalName -} diff --git a/internal/rules_cache_test.go b/internal/rules_cache_test.go deleted file mode 100644 index 992442578..000000000 --- a/internal/rules_cache_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import "testing" - -func TestRulesCache(t *testing.T) { - testcases := []struct { - input string - isWeb bool - output string - }{ - {input: "name1", isWeb: true, output: "WebTransaction/Go/name1"}, - {input: "name1", isWeb: false, output: "OtherTransaction/Go/name1"}, - {input: "name2", isWeb: true, output: "WebTransaction/Go/name2"}, - {input: "name3", isWeb: true, output: "WebTransaction/Go/name3"}, - {input: "zap/123/zip", isWeb: false, output: "OtherTransaction/Go/zap/*/zip"}, - {input: "zap/45/zip", isWeb: false, output: "OtherTransaction/Go/zap/*/zip"}, - } - - cache := newRulesCache(len(testcases)) - for _, tc := range testcases { - // Test that nothing is in the cache before population. - if out := cache.find(tc.input, tc.isWeb); out != "" { - t.Error(out, tc.input, tc.isWeb) - } - } - for _, tc := range testcases { - cache.set(tc.input, tc.isWeb, tc.output) - } - for _, tc := range testcases { - // Test that everything is now in the cache as expected. - if out := cache.find(tc.input, tc.isWeb); out != tc.output { - t.Error(out, tc.input, tc.isWeb, tc.output) - } - } -} - -func TestRulesCacheLimit(t *testing.T) { - cache := newRulesCache(1) - cache.set("name1", true, "WebTransaction/Go/name1") - cache.set("name1", false, "OtherTransaction/Go/name1") - if out := cache.find("name1", true); out != "WebTransaction/Go/name1" { - t.Error(out) - } - if out := cache.find("name1", false); out != "" { - t.Error(out) - } -} - -func TestRulesCacheNil(t *testing.T) { - var cache *rulesCache - // No panics should happen if the rules cache pointer is nil. - if out := cache.find("name1", true); "" != out { - t.Error(out) - } - cache.set("name1", false, "OtherTransaction/Go/name1") -} diff --git a/internal/sampler.go b/internal/sampler.go deleted file mode 100644 index 8a4526f39..000000000 --- a/internal/sampler.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "runtime" - "time" - - "github.com/newrelic/go-agent/internal/logger" - "github.com/newrelic/go-agent/internal/sysinfo" -) - -// Sample is a system/runtime snapshot. -type Sample struct { - when time.Time - memStats runtime.MemStats - usage sysinfo.Usage - numGoroutine int - numCPU int -} - -func bytesToMebibytesFloat(bts uint64) float64 { - return float64(bts) / (1024 * 1024) -} - -// GetSample gathers a new Sample. -func GetSample(now time.Time, lg logger.Logger) *Sample { - s := Sample{ - when: now, - numGoroutine: runtime.NumGoroutine(), - numCPU: runtime.NumCPU(), - } - - if usage, err := sysinfo.GetUsage(); err == nil { - s.usage = usage - } else { - lg.Warn("unable to usage", map[string]interface{}{ - "error": err.Error(), - }) - } - - runtime.ReadMemStats(&s.memStats) - - return &s -} - -type cpuStats struct { - used time.Duration - fraction float64 // used / (elapsed * numCPU) -} - -// Stats contains system information for a period of time. -type Stats struct { - numGoroutine int - allocBytes uint64 - heapObjects uint64 - user cpuStats - system cpuStats - gcPauseFraction float64 - deltaNumGC uint32 - deltaPauseTotal time.Duration - minPause time.Duration - maxPause time.Duration -} - -// Samples is used as the parameter to GetStats to avoid mixing up the previous -// and current sample. -type Samples struct { - Previous *Sample - Current *Sample -} - -// GetStats combines two Samples into a Stats. -func GetStats(ss Samples) Stats { - cur := ss.Current - prev := ss.Previous - elapsed := cur.when.Sub(prev.when) - - s := Stats{ - numGoroutine: cur.numGoroutine, - allocBytes: cur.memStats.Alloc, - heapObjects: cur.memStats.HeapObjects, - } - - // CPU Utilization - totalCPUSeconds := elapsed.Seconds() * float64(cur.numCPU) - if prev.usage.User != 0 && cur.usage.User > prev.usage.User { - s.user.used = cur.usage.User - prev.usage.User - s.user.fraction = s.user.used.Seconds() / totalCPUSeconds - } - if prev.usage.System != 0 && cur.usage.System > prev.usage.System { - s.system.used = cur.usage.System - prev.usage.System - s.system.fraction = s.system.used.Seconds() / totalCPUSeconds - } - - // GC Pause Fraction - deltaPauseTotalNs := cur.memStats.PauseTotalNs - prev.memStats.PauseTotalNs - frac := float64(deltaPauseTotalNs) / float64(elapsed.Nanoseconds()) - s.gcPauseFraction = frac - - // GC Pauses - if deltaNumGC := cur.memStats.NumGC - prev.memStats.NumGC; deltaNumGC > 0 { - // In case more than 256 pauses have happened between samples - // and we are examining a subset of the pauses, we ensure that - // the min and max are not on the same side of the average by - // using the average as the starting min and max. - maxPauseNs := deltaPauseTotalNs / uint64(deltaNumGC) - minPauseNs := deltaPauseTotalNs / uint64(deltaNumGC) - for i := prev.memStats.NumGC + 1; i <= cur.memStats.NumGC; i++ { - pause := cur.memStats.PauseNs[(i+255)%256] - if pause > maxPauseNs { - maxPauseNs = pause - } - if pause < minPauseNs { - minPauseNs = pause - } - } - s.deltaPauseTotal = time.Duration(deltaPauseTotalNs) * time.Nanosecond - s.deltaNumGC = deltaNumGC - s.minPause = time.Duration(minPauseNs) * time.Nanosecond - s.maxPause = time.Duration(maxPauseNs) * time.Nanosecond - } - - return s -} - -// MergeIntoHarvest implements Harvestable. -func (s Stats) MergeIntoHarvest(h *Harvest) { - h.Metrics.addValue(heapObjectsAllocated, "", float64(s.heapObjects), forced) - h.Metrics.addValue(runGoroutine, "", float64(s.numGoroutine), forced) - h.Metrics.addValueExclusive(memoryPhysical, "", bytesToMebibytesFloat(s.allocBytes), 0, forced) - h.Metrics.addValueExclusive(cpuUserUtilization, "", s.user.fraction, 0, forced) - h.Metrics.addValueExclusive(cpuSystemUtilization, "", s.system.fraction, 0, forced) - h.Metrics.addValue(cpuUserTime, "", s.user.used.Seconds(), forced) - h.Metrics.addValue(cpuSystemTime, "", s.system.used.Seconds(), forced) - h.Metrics.addValueExclusive(gcPauseFraction, "", s.gcPauseFraction, 0, forced) - if s.deltaNumGC > 0 { - h.Metrics.add(gcPauses, "", metricData{ - countSatisfied: float64(s.deltaNumGC), - totalTolerated: s.deltaPauseTotal.Seconds(), - exclusiveFailed: 0, - min: s.minPause.Seconds(), - max: s.maxPause.Seconds(), - sumSquares: s.deltaPauseTotal.Seconds() * s.deltaPauseTotal.Seconds(), - }, forced) - } -} diff --git a/internal/sampler_test.go b/internal/sampler_test.go deleted file mode 100644 index 12f05830f..000000000 --- a/internal/sampler_test.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "testing" - "time" - - "github.com/newrelic/go-agent/internal/logger" -) - -func TestGetSample(t *testing.T) { - now := time.Now() - sample := GetSample(now, logger.ShimLogger{}) - if nil == sample { - t.Fatal(sample) - } - if now != sample.when { - t.Error(now, sample.when) - } - if sample.numGoroutine <= 0 { - t.Error(sample.numGoroutine) - } - if sample.numCPU <= 0 { - t.Error(sample.numCPU) - } - if sample.memStats.HeapObjects == 0 { - t.Error(sample.memStats.HeapObjects) - } -} - -func TestMetricsCreated(t *testing.T) { - now := time.Now() - h := NewHarvest(now, &DfltHarvestCfgr{}) - - stats := Stats{ - heapObjects: 5 * 1000, - numGoroutine: 23, - allocBytes: 37 * 1024 * 1024, - user: cpuStats{ - used: 20 * time.Millisecond, - fraction: 0.01, - }, - system: cpuStats{ - used: 40 * time.Millisecond, - fraction: 0.02, - }, - gcPauseFraction: 3e-05, - deltaNumGC: 2, - deltaPauseTotal: 500 * time.Microsecond, - minPause: 100 * time.Microsecond, - maxPause: 400 * time.Microsecond, - } - - stats.MergeIntoHarvest(h) - - ExpectMetrics(t, h.Metrics, []WantMetric{ - {"Memory/Heap/AllocatedObjects", "", true, []float64{1, 5000, 5000, 5000, 5000, 25000000}}, - {"Memory/Physical", "", true, []float64{1, 37, 0, 37, 37, 1369}}, - {"CPU/User Time", "", true, []float64{1, 0.02, 0.02, 0.02, 0.02, 0.0004}}, - {"CPU/System Time", "", true, []float64{1, 0.04, 0.04, 0.04, 0.04, 0.0016}}, - {"CPU/User/Utilization", "", true, []float64{1, 0.01, 0, 0.01, 0.01, 0.0001}}, - {"CPU/System/Utilization", "", true, []float64{1, 0.02, 0, 0.02, 0.02, 0.0004}}, - {"Go/Runtime/Goroutines", "", true, []float64{1, 23, 23, 23, 23, 529}}, - {"GC/System/Pause Fraction", "", true, []float64{1, 3e-05, 0, 3e-05, 3e-05, 9e-10}}, - {"GC/System/Pauses", "", true, []float64{2, 0.0005, 0, 0.0001, 0.0004, 2.5e-7}}, - }) -} - -func TestMetricsCreatedEmpty(t *testing.T) { - now := time.Now() - h := NewHarvest(now, &DfltHarvestCfgr{}) - stats := Stats{} - - stats.MergeIntoHarvest(h) - - ExpectMetrics(t, h.Metrics, []WantMetric{ - {"Memory/Heap/AllocatedObjects", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Memory/Physical", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"CPU/User Time", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"CPU/System Time", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"CPU/User/Utilization", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"CPU/System/Utilization", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"Go/Runtime/Goroutines", "", true, []float64{1, 0, 0, 0, 0, 0}}, - {"GC/System/Pause Fraction", "", true, []float64{1, 0, 0, 0, 0, 0}}, - }) -} diff --git a/internal/security_policies.go b/internal/security_policies.go deleted file mode 100644 index aec1af208..000000000 --- a/internal/security_policies.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "fmt" - "reflect" -) - -// Security policies documentation: -// https://source.datanerd.us/agents/agent-specs/blob/master/Language-Agent-Security-Policies.md - -// SecurityPolicies contains the security policies. -type SecurityPolicies struct { - RecordSQL securityPolicy `json:"record_sql"` - AttributesInclude securityPolicy `json:"attributes_include"` - AllowRawExceptionMessages securityPolicy `json:"allow_raw_exception_messages"` - CustomEvents securityPolicy `json:"custom_events"` - CustomParameters securityPolicy `json:"custom_parameters"` -} - -// PointerIfPopulated returns a reference to the security policies if they have -// been populated from JSON. -func (sp *SecurityPolicies) PointerIfPopulated() *SecurityPolicies { - emptyPolicies := SecurityPolicies{} - if nil != sp && *sp != emptyPolicies { - return sp - } - return nil -} - -type securityPolicy struct { - EnabledVal *bool `json:"enabled"` -} - -func (p *securityPolicy) Enabled() bool { return nil == p.EnabledVal || *p.EnabledVal } -func (p *securityPolicy) SetEnabled(enabled bool) { p.EnabledVal = &enabled } -func (p *securityPolicy) IsSet() bool { return nil != p.EnabledVal } - -type policyer interface { - SetEnabled(bool) - IsSet() bool -} - -// UnmarshalJSON decodes security policies sent from the preconnect endpoint. -func (sp *SecurityPolicies) UnmarshalJSON(data []byte) (er error) { - defer func() { - // Zero out all fields if there is an error to ensure that the - // populated check works. - if er != nil { - *sp = SecurityPolicies{} - } - }() - - var raw map[string]struct { - Enabled bool `json:"enabled"` - Required bool `json:"required"` - } - err := json.Unmarshal(data, &raw) - if err != nil { - return fmt.Errorf("unable to unmarshal security policies: %v", err) - } - - knownPolicies := make(map[string]policyer) - - spv := reflect.ValueOf(sp).Elem() - for i := 0; i < spv.NumField(); i++ { - fieldAddress := spv.Field(i).Addr() - field := fieldAddress.Interface().(policyer) - name := spv.Type().Field(i).Tag.Get("json") - knownPolicies[name] = field - } - - for name, policy := range raw { - p, ok := knownPolicies[name] - if !ok { - if policy.Required { - return errUnknownRequiredPolicy{name: name} - } - } else { - p.SetEnabled(policy.Enabled) - } - } - for name, policy := range knownPolicies { - if !policy.IsSet() { - return errUnsetPolicy{name: name} - } - } - return nil -} - -type errUnknownRequiredPolicy struct{ name string } - -func (err errUnknownRequiredPolicy) Error() string { - return fmt.Sprintf("policy '%s' is unrecognized, please check for a newer agent version or contact support", err.name) -} - -type errUnsetPolicy struct{ name string } - -func (err errUnsetPolicy) Error() string { - return fmt.Sprintf("policy '%s' not received, please contact support", err.name) -} - -func isDisconnectSecurityPolicyError(e error) bool { - if _, ok := e.(errUnknownRequiredPolicy); ok { - return true - } - if _, ok := e.(errUnsetPolicy); ok { - return true - } - return false -} diff --git a/internal/security_policies_test.go b/internal/security_policies_test.go deleted file mode 100644 index 2ea2428c9..000000000 --- a/internal/security_policies_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "testing" -) - -func testBool(t *testing.T, name string, expected, got bool) { - if expected != got { - t.Errorf("%v: expected=%v got=%v", name, expected, got) - } -} - -func TestSecurityPoliciesPresent(t *testing.T) { - inputJSON := []byte(`{ - "record_sql": { "enabled": false, "required": false }, - "attributes_include": { "enabled": false, "required": false }, - "allow_raw_exception_messages": { "enabled": false, "required": false }, - "custom_events": { "enabled": false, "required": false }, - "custom_parameters": { "enabled": false, "required": false }, - "custom_instrumentation_editor": { "enabled": false, "required": false }, - "message_parameters": { "enabled": false, "required": false }, - "job_arguments": { "enabled": false, "required": false } - }`) - var policies SecurityPolicies - err := json.Unmarshal(inputJSON, &policies) - if nil != err { - t.Fatal(err) - } - connectJSON, err := json.Marshal(policies) - if nil != err { - t.Fatal(err) - } - expectJSON := CompactJSONString(`{ - "record_sql": { "enabled": false }, - "attributes_include": { "enabled": false }, - "allow_raw_exception_messages": { "enabled": false }, - "custom_events": { "enabled": false }, - "custom_parameters": { "enabled": false } - }`) - if string(connectJSON) != expectJSON { - t.Error(string(connectJSON), expectJSON) - } - testBool(t, "PointerIfPopulated", true, nil != policies.PointerIfPopulated()) - testBool(t, "RecordSQLEnabled", false, policies.RecordSQL.Enabled()) - testBool(t, "AttributesIncludeEnabled", false, policies.AttributesInclude.Enabled()) - testBool(t, "AllowRawExceptionMessages", false, policies.AllowRawExceptionMessages.Enabled()) - testBool(t, "CustomEventsEnabled", false, policies.CustomEvents.Enabled()) - testBool(t, "CustomParametersEnabled", false, policies.CustomParameters.Enabled()) -} - -func TestNilSecurityPolicies(t *testing.T) { - var policies SecurityPolicies - testBool(t, "PointerIfPopulated", false, nil != policies.PointerIfPopulated()) - testBool(t, "RecordSQLEnabled", true, policies.RecordSQL.Enabled()) - testBool(t, "AttributesIncludeEnabled", true, policies.AttributesInclude.Enabled()) - testBool(t, "AllowRawExceptionMessages", true, policies.AllowRawExceptionMessages.Enabled()) - testBool(t, "CustomEventsEnabled", true, policies.CustomEvents.Enabled()) - testBool(t, "CustomParametersEnabled", true, policies.CustomParameters.Enabled()) -} - -func TestUnknownRequiredPolicy(t *testing.T) { - inputJSON := []byte(`{ - "record_sql": { "enabled": false, "required": false }, - "attributes_include": { "enabled": false, "required": false }, - "allow_raw_exception_messages": { "enabled": false, "required": false }, - "custom_events": { "enabled": false, "required": false }, - "custom_parameters": { "enabled": false, "required": false }, - "custom_instrumentation_editor": { "enabled": false, "required": false }, - "message_parameters": { "enabled": false, "required": false }, - "job_arguments": { "enabled": false, "required": false }, - "unknown_policy": { "enabled": false, "required": true } - }`) - var policies SecurityPolicies - err := json.Unmarshal(inputJSON, &policies) - if nil == err { - t.Fatal(err) - } - testBool(t, "PointerIfPopulated", false, nil != policies.PointerIfPopulated()) - testBool(t, "unknown required policy should be disconnect", true, isDisconnectSecurityPolicyError(err)) -} - -func TestSecurityPolicyMissing(t *testing.T) { - inputJSON := []byte(`{ - "record_sql": { "enabled": false, "required": false }, - "attributes_include": { "enabled": false, "required": false }, - "allow_raw_exception_messages": { "enabled": false, "required": false }, - "custom_events": { "enabled": false, "required": false }, - "request_parameters": { "enabled": false, "required": false } - }`) - var policies SecurityPolicies - err := json.Unmarshal(inputJSON, &policies) - _, ok := err.(errUnsetPolicy) - if !ok { - t.Fatal(err) - } - testBool(t, "PointerIfPopulated", false, nil != policies.PointerIfPopulated()) - testBool(t, "missing policy should be disconnect", true, isDisconnectSecurityPolicyError(err)) -} - -func TestMalformedPolicies(t *testing.T) { - inputJSON := []byte(`{`) - var policies SecurityPolicies - err := json.Unmarshal(inputJSON, &policies) - if nil == err { - t.Fatal(err) - } - testBool(t, "malformed policies should not be disconnect", false, isDisconnectSecurityPolicyError(err)) -} diff --git a/internal/segment_terms.go b/internal/segment_terms.go deleted file mode 100644 index 440908529..000000000 --- a/internal/segment_terms.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -// https://newrelic.atlassian.net/wiki/display/eng/Language+agent+transaction+segment+terms+rules - -import ( - "encoding/json" - "strings" -) - -const ( - placeholder = "*" - separator = "/" -) - -type segmentRule struct { - Prefix string `json:"prefix"` - Terms []string `json:"terms"` - TermsMap map[string]struct{} -} - -// segmentRules is keyed by each segmentRule's Prefix field with any trailing -// slash removed. -type segmentRules map[string]*segmentRule - -func buildTermsMap(terms []string) map[string]struct{} { - m := make(map[string]struct{}, len(terms)) - for _, t := range terms { - m[t] = struct{}{} - } - return m -} - -func (rules *segmentRules) UnmarshalJSON(b []byte) error { - var raw []*segmentRule - - if err := json.Unmarshal(b, &raw); nil != err { - return err - } - - rs := make(map[string]*segmentRule) - - for _, rule := range raw { - prefix := strings.TrimSuffix(rule.Prefix, "/") - if len(strings.Split(prefix, "/")) != 2 { - // TODO - // Warn("invalid segment term rule prefix", - // {"prefix": rule.Prefix}) - continue - } - - if nil == rule.Terms { - // TODO - // Warn("segment term rule has missing terms", - // {"prefix": rule.Prefix}) - continue - } - - rule.TermsMap = buildTermsMap(rule.Terms) - - rs[prefix] = rule - } - - *rules = rs - return nil -} - -func (rule *segmentRule) apply(name string) string { - if !strings.HasPrefix(name, rule.Prefix) { - return name - } - - s := strings.TrimPrefix(name, rule.Prefix) - - leadingSlash := "" - if strings.HasPrefix(s, separator) { - leadingSlash = separator - s = strings.TrimPrefix(s, separator) - } - - if "" != s { - segments := strings.Split(s, separator) - - for i, segment := range segments { - _, allowed := rule.TermsMap[segment] - if allowed { - segments[i] = segment - } else { - segments[i] = placeholder - } - } - - segments = collapsePlaceholders(segments) - s = strings.Join(segments, separator) - } - - return rule.Prefix + leadingSlash + s -} - -func (rules segmentRules) apply(name string) string { - if nil == rules { - return name - } - - rule, ok := rules[firstTwoSegments(name)] - if !ok { - return name - } - - return rule.apply(name) -} - -func firstTwoSegments(name string) string { - firstSlashIdx := strings.Index(name, separator) - if firstSlashIdx == -1 { - return name - } - - secondSlashIdx := strings.Index(name[firstSlashIdx+1:], separator) - if secondSlashIdx == -1 { - return name - } - - return name[0 : firstSlashIdx+secondSlashIdx+1] -} - -func collapsePlaceholders(segments []string) []string { - j := 0 - prevStar := false - for i := 0; i < len(segments); i++ { - segment := segments[i] - if placeholder == segment { - if prevStar { - continue - } - segments[j] = placeholder - j++ - prevStar = true - } else { - segments[j] = segment - j++ - prevStar = false - } - } - return segments[0:j] -} diff --git a/internal/segment_terms_test.go b/internal/segment_terms_test.go deleted file mode 100644 index 4475bb8ba..000000000 --- a/internal/segment_terms_test.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "strings" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestCrossAgentSegmentTerms(t *testing.T) { - var tcs []struct { - Testname string `json:"testname"` - Rules segmentRules `json:"transaction_segment_terms"` - Tests []struct { - Input string `json:"input"` - Expected string `json:"expected"` - } `json:"tests"` - } - - err := crossagent.ReadJSON("transaction_segment_terms.json", &tcs) - if err != nil { - t.Fatal(err) - } - - for _, tc := range tcs { - for _, test := range tc.Tests { - out := tc.Rules.apply(test.Input) - if out != test.Expected { - t.Fatal(tc.Testname, test.Input, out, test.Expected) - } - } - } -} - -func TestSegmentTerms(t *testing.T) { - js := `[ - { - "prefix":"WebTransaction\/Uri", - "terms":[ - "two", - "Users", - "willhf", - "dev", - "php", - "one", - "alpha", - "zap" - ] - } - ]` - var rules segmentRules - if err := json.Unmarshal([]byte(js), &rules); nil != err { - t.Fatal(err) - } - - out := rules.apply("WebTransaction/Uri/pen/two/pencil/dev/paper") - if out != "WebTransaction/Uri/*/two/*/dev/*" { - t.Fatal(out) - } -} - -func TestEmptySegmentTerms(t *testing.T) { - var rules segmentRules - - input := "my/name" - out := rules.apply(input) - if out != input { - t.Error(input, out) - } -} - -func BenchmarkSegmentTerms(b *testing.B) { - js := `[ - { - "prefix":"WebTransaction\/Uri", - "terms":[ - "two", - "Users", - "willhf", - "dev", - "php", - "one", - "alpha", - "zap" - ] - } - ]` - var rules segmentRules - if err := json.Unmarshal([]byte(js), &rules); nil != err { - b.Fatal(err) - } - - b.ResetTimer() - b.ReportAllocs() - - input := "WebTransaction/Uri/pen/two/pencil/dev/paper" - expected := "WebTransaction/Uri/*/two/*/dev/*" - for i := 0; i < b.N; i++ { - out := rules.apply(input) - if out != expected { - b.Fatal(out, expected) - } - } -} - -func TestCollapsePlaceholders(t *testing.T) { - testcases := []struct { - input string - expect string - }{ - {input: "", expect: ""}, - {input: "/", expect: "/"}, - {input: "*", expect: "*"}, - {input: "*/*", expect: "*"}, - {input: "a/b/c", expect: "a/b/c"}, - {input: "*/*/*", expect: "*"}, - {input: "a/*/*/*/b", expect: "a/*/b"}, - {input: "a/b/*/*/*/", expect: "a/b/*/"}, - {input: "a/b/*/*/*", expect: "a/b/*"}, - {input: "*/*/a/b/*/*/*", expect: "*/a/b/*"}, - {input: "*/*/a/b/*/c/*/*/d/e/*/*/*", expect: "*/a/b/*/c/*/d/e/*"}, - {input: "a/*/b", expect: "a/*/b"}, - } - - for _, tc := range testcases { - segments := strings.Split(tc.input, "/") - segments = collapsePlaceholders(segments) - out := strings.Join(segments, "/") - if out != tc.expect { - t.Error(tc.input, tc.expect, out) - } - } -} diff --git a/internal/serverless.go b/internal/serverless.go deleted file mode 100644 index 9f339a588..000000000 --- a/internal/serverless.go +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "strings" - "sync" - "time" - - "github.com/newrelic/go-agent/internal/logger" -) - -const ( - lambdaMetadataVersion = 2 - - // AgentLanguage is used in the connect JSON and the Lambda JSON. - AgentLanguage = "go" -) - -// ServerlessHarvest is used to store and log data when the agent is running in -// serverless mode. -type ServerlessHarvest struct { - logger logger.Logger - version string - awsExecutionEnv string - - // The Lambda handler could be using multiple goroutines so we use a - // mutex to prevent race conditions. - sync.Mutex - harvest *Harvest -} - -// NewServerlessHarvest creates a new ServerlessHarvest. -func NewServerlessHarvest(logger logger.Logger, version string, getEnv func(string) string) *ServerlessHarvest { - return &ServerlessHarvest{ - logger: logger, - version: version, - awsExecutionEnv: getEnv("AWS_EXECUTION_ENV"), - - // We can use a default HarvestConfigured parameter because - // serverless mode doesn't have a connect, and therefore won't - // have custom event limits from the server. - harvest: NewHarvest(time.Now(), &DfltHarvestCfgr{}), - } -} - -// Consume adds data to the harvest. -func (sh *ServerlessHarvest) Consume(data Harvestable) { - if nil == sh { - return - } - sh.Lock() - defer sh.Unlock() - - data.MergeIntoHarvest(sh.harvest) -} - -func (sh *ServerlessHarvest) swapHarvest() *Harvest { - sh.Lock() - defer sh.Unlock() - - h := sh.harvest - sh.harvest = NewHarvest(time.Now(), &DfltHarvestCfgr{}) - return h -} - -// Write logs the data in the format described by: -// https://source.datanerd.us/agents/agent-specs/blob/master/Lambda.md -func (sh *ServerlessHarvest) Write(arn string, writer io.Writer) { - if nil == sh { - return - } - harvest := sh.swapHarvest() - payloads := harvest.Payloads(false) - // Note that *json.RawMessage (instead of json.RawMessage) is used to - // support older Go versions: https://go-review.googlesource.com/c/go/+/21811/ - harvestPayloads := make(map[string]*json.RawMessage, len(payloads)) - for _, p := range payloads { - agentRunID := "" - cmd := p.EndpointMethod() - data, err := p.Data(agentRunID, time.Now()) - if err != nil { - sh.logger.Error("error creating payload json", map[string]interface{}{ - "command": cmd, - "error": err.Error(), - }) - continue - } - if nil == data { - continue - } - // NOTE! This code relies on the fact that each payload is - // using a different endpoint method. Sometimes the transaction - // events payload might be split, but since there is only one - // transaction event per serverless transaction, that's not an - // issue. Likewise, if we ever split normal transaction events - // apart from synthetics events, the transaction will either be - // normal or synthetic, so that won't be an issue. Log an error - // if this happens for future defensiveness. - if _, ok := harvestPayloads[cmd]; ok { - sh.logger.Error("data with duplicate command name lost", map[string]interface{}{ - "command": cmd, - }) - } - d := json.RawMessage(data) - harvestPayloads[cmd] = &d - } - - if len(harvestPayloads) == 0 { - // The harvest may not contain any data if the serverless - // transaction was ignored. - return - } - - data, err := json.Marshal(harvestPayloads) - if nil != err { - sh.logger.Error("error creating serverless data json", map[string]interface{}{ - "error": err.Error(), - }) - return - } - - var dataBuf bytes.Buffer - gz := gzip.NewWriter(&dataBuf) - gz.Write(data) - gz.Flush() - gz.Close() - - js, err := json.Marshal([]interface{}{ - lambdaMetadataVersion, - "NR_LAMBDA_MONITORING", - struct { - MetadataVersion int `json:"metadata_version"` - ARN string `json:"arn,omitempty"` - ProtocolVersion int `json:"protocol_version"` - ExecutionEnvironment string `json:"execution_environment,omitempty"` - AgentVersion string `json:"agent_version"` - AgentLanguage string `json:"agent_language"` - }{ - MetadataVersion: lambdaMetadataVersion, - ProtocolVersion: ProcotolVersion, - AgentVersion: sh.version, - ExecutionEnvironment: sh.awsExecutionEnv, - ARN: arn, - AgentLanguage: AgentLanguage, - }, - base64.StdEncoding.EncodeToString(dataBuf.Bytes()), - }) - - if err != nil { - sh.logger.Error("error creating serverless json", map[string]interface{}{ - "error": err.Error(), - }) - return - } - - fmt.Fprintln(writer, string(js)) -} - -// ParseServerlessPayload exists for testing. -func ParseServerlessPayload(data []byte) (metadata, uncompressedData map[string]json.RawMessage, err error) { - var arr [4]json.RawMessage - if err = json.Unmarshal(data, &arr); nil != err { - err = fmt.Errorf("unable to unmarshal serverless data array: %v", err) - return - } - var dataJSON []byte - compressed := strings.Trim(string(arr[3]), `"`) - if dataJSON, err = decodeUncompress(compressed); nil != err { - err = fmt.Errorf("unable to uncompress serverless data: %v", err) - return - } - if err = json.Unmarshal(dataJSON, &uncompressedData); nil != err { - err = fmt.Errorf("unable to unmarshal uncompressed serverless data: %v", err) - return - } - if err = json.Unmarshal(arr[2], &metadata); nil != err { - err = fmt.Errorf("unable to unmarshal serverless metadata: %v", err) - return - } - return -} - -func decodeUncompress(input string) ([]byte, error) { - decoded, err := base64.StdEncoding.DecodeString(input) - if nil != err { - return nil, err - } - - buf := bytes.NewBuffer(decoded) - gz, err := gzip.NewReader(buf) - if nil != err { - return nil, err - } - var out bytes.Buffer - io.Copy(&out, gz) - gz.Close() - - return out.Bytes(), nil -} - -// ServerlessWriter is implemented by newrelic.Application. -type ServerlessWriter interface { - ServerlessWrite(arn string, writer io.Writer) -} - -// ServerlessWrite exists to avoid type assertion in the nrlambda integration -// package. -func ServerlessWrite(app interface{}, arn string, writer io.Writer) { - if s, ok := app.(ServerlessWriter); ok { - s.ServerlessWrite(arn, writer) - } -} diff --git a/internal/serverless_test.go b/internal/serverless_test.go deleted file mode 100644 index 5c62d1f7f..000000000 --- a/internal/serverless_test.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "strings" - "testing" - "time" - - "github.com/newrelic/go-agent/internal/logger" -) - -func serverlessGetenvShim(s string) string { - if s == "AWS_EXECUTION_ENV" { - return "the-execution-env" - } - return "" -} - -func TestServerlessHarvest(t *testing.T) { - // Test the expected ServerlessHarvest use. - sh := NewServerlessHarvest(logger.ShimLogger{}, "the-version", serverlessGetenvShim) - event, err := CreateCustomEvent("myEvent", nil, time.Now()) - if nil != err { - t.Fatal(err) - } - sh.Consume(event) - buf := &bytes.Buffer{} - sh.Write("arn", buf) - metadata, data, err := ParseServerlessPayload(buf.Bytes()) - if nil != err { - t.Fatal(err) - } - if v := string(metadata["metadata_version"]); v != `2` { - t.Error(v) - } - if v := string(metadata["arn"]); v != `"arn"` { - t.Error(v) - } - if v := string(metadata["protocol_version"]); v != `17` { - t.Error(v) - } - if v := string(metadata["execution_environment"]); v != `"the-execution-env"` { - t.Error(v) - } - if v := string(metadata["agent_version"]); v != `"the-version"` { - t.Error(v) - } - if v := string(metadata["agent_language"]); v != `"go"` { - t.Error(v) - } - eventData := string(data["custom_event_data"]) - if !strings.Contains(eventData, `"type":"myEvent"`) { - t.Error(eventData) - } - if len(data) != 1 { - t.Fatal(data) - } - // Test that the harvest was replaced with a new harvest. - buf = &bytes.Buffer{} - sh.Write("arn", buf) - if 0 != buf.Len() { - t.Error(buf.String()) - } -} - -func TestServerlessHarvestNil(t *testing.T) { - // The public ServerlessHarvest methods should not panic if the - // receiver is nil. - var sh *ServerlessHarvest - event, err := CreateCustomEvent("myEvent", nil, time.Now()) - if nil != err { - t.Fatal(err) - } - sh.Consume(event) - buf := &bytes.Buffer{} - sh.Write("arn", buf) -} - -func TestServerlessHarvestEmpty(t *testing.T) { - // Test that ServerlessHarvest.Write doesn't do anything if the harvest - // is empty. - sh := NewServerlessHarvest(logger.ShimLogger{}, "the-version", serverlessGetenvShim) - buf := &bytes.Buffer{} - sh.Write("arn", buf) - if 0 != buf.Len() { - t.Error(buf.String()) - } -} - -func BenchmarkServerless(b *testing.B) { - // The JSON creation in ServerlessHarvest.Write has not been optimized. - // This benchmark would be useful for doing so. - sh := NewServerlessHarvest(logger.ShimLogger{}, "the-version", serverlessGetenvShim) - event, err := CreateCustomEvent("myEvent", nil, time.Now()) - if nil != err { - b.Fatal(err) - } - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - sh.Consume(event) - buf := &bytes.Buffer{} - sh.Write("arn", buf) - if buf.Len() == 0 { - b.Fatal(buf.String()) - } - } -} diff --git a/internal/slow_queries.go b/internal/slow_queries.go deleted file mode 100644 index 527ed7fd2..000000000 --- a/internal/slow_queries.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "container/heap" - "hash/fnv" - "time" - - "github.com/newrelic/go-agent/internal/jsonx" -) - -type queryParameters map[string]interface{} - -func vetQueryParameters(params map[string]interface{}) (queryParameters, error) { - if nil == params { - return nil, nil - } - // Copying the parameters into a new map is safer than modifying the map - // from the customer. - vetted := make(map[string]interface{}) - var retErr error - for key, val := range params { - val, err := ValidateUserAttribute(key, val) - if nil != err { - retErr = err - continue - } - vetted[key] = val - } - return queryParameters(vetted), retErr -} - -func (q queryParameters) WriteJSON(buf *bytes.Buffer) { - buf.WriteByte('{') - w := jsonFieldsWriter{buf: buf} - for key, val := range q { - writeAttributeValueJSON(&w, key, val) - } - buf.WriteByte('}') -} - -// https://source.datanerd.us/agents/agent-specs/blob/master/Slow-SQLs-LEGACY.md - -// slowQueryInstance represents a single datastore call. -type slowQueryInstance struct { - // Fields populated right after the datastore segment finishes: - - Duration time.Duration - DatastoreMetric string - ParameterizedQuery string - QueryParameters queryParameters - Host string - PortPathOrID string - DatabaseName string - StackTrace StackTrace - - TxnEvent -} - -// Aggregation is performed to avoid reporting multiple slow queries with same -// query string. Since some datastore segments may be below the slow query -// threshold, the aggregation fields Count, Total, and Min should be taken with -// a grain of salt. -type slowQuery struct { - Count int32 // number of times the query has been observed - Total time.Duration // cummulative duration - Min time.Duration // minimum observed duration - - // When Count > 1, slowQueryInstance contains values from the slowest - // observation. - slowQueryInstance -} - -type slowQueries struct { - priorityQueue []*slowQuery - // lookup maps query strings to indices in the priorityQueue - lookup map[string]int -} - -func (slows *slowQueries) Len() int { - return len(slows.priorityQueue) -} -func (slows *slowQueries) Less(i, j int) bool { - pq := slows.priorityQueue - return pq[i].Duration < pq[j].Duration -} -func (slows *slowQueries) Swap(i, j int) { - pq := slows.priorityQueue - si := pq[i] - sj := pq[j] - pq[i], pq[j] = pq[j], pq[i] - slows.lookup[si.ParameterizedQuery] = j - slows.lookup[sj.ParameterizedQuery] = i -} - -// Push and Pop are unused: only heap.Init and heap.Fix are used. -func (slows *slowQueries) Push(x interface{}) {} -func (slows *slowQueries) Pop() interface{} { return nil } - -func newSlowQueries(max int) *slowQueries { - return &slowQueries{ - lookup: make(map[string]int, max), - priorityQueue: make([]*slowQuery, 0, max), - } -} - -// Merge is used to merge slow queries from the transaction into the harvest. -func (slows *slowQueries) Merge(other *slowQueries, txnEvent TxnEvent) { - for _, s := range other.priorityQueue { - cp := *s - cp.TxnEvent = txnEvent - slows.observe(cp) - } -} - -// merge aggregates the observations from two slow queries with the same Query. -func (slow *slowQuery) merge(other slowQuery) { - slow.Count += other.Count - slow.Total += other.Total - - if other.Min < slow.Min { - slow.Min = other.Min - } - if other.Duration > slow.Duration { - slow.slowQueryInstance = other.slowQueryInstance - } -} - -func (slows *slowQueries) observeInstance(slow slowQueryInstance) { - slows.observe(slowQuery{ - Count: 1, - Total: slow.Duration, - Min: slow.Duration, - slowQueryInstance: slow, - }) -} - -func (slows *slowQueries) insertAtIndex(slow slowQuery, idx int) { - cpy := new(slowQuery) - *cpy = slow - slows.priorityQueue[idx] = cpy - slows.lookup[slow.ParameterizedQuery] = idx - heap.Fix(slows, idx) -} - -func (slows *slowQueries) observe(slow slowQuery) { - // Has the query has previously been observed? - if idx, ok := slows.lookup[slow.ParameterizedQuery]; ok { - slows.priorityQueue[idx].merge(slow) - heap.Fix(slows, idx) - return - } - // Has the collection reached max capacity? - if len(slows.priorityQueue) < cap(slows.priorityQueue) { - idx := len(slows.priorityQueue) - slows.priorityQueue = slows.priorityQueue[0 : idx+1] - slows.insertAtIndex(slow, idx) - return - } - // Is this query slower than the existing fastest? - fastest := slows.priorityQueue[0] - if slow.Duration > fastest.Duration { - delete(slows.lookup, fastest.ParameterizedQuery) - slows.insertAtIndex(slow, 0) - return - } -} - -// The third element of the slow query JSON should be a hash of the query -// string. This hash may be used by backend services to aggregate queries which -// have the have the same query string. It is unknown if this actually used. -func makeSlowQueryID(query string) uint32 { - h := fnv.New32a() - h.Write([]byte(query)) - return h.Sum32() -} - -func (slow *slowQuery) WriteJSON(buf *bytes.Buffer) { - buf.WriteByte('[') - jsonx.AppendString(buf, slow.TxnEvent.FinalName) - buf.WriteByte(',') - // Include request.uri if it is included in any destination. - // TODO: Change this to the transaction trace segment destination - // once transaction trace segment attribute configuration has been - // added. - uri, _ := slow.TxnEvent.Attrs.GetAgentValue(attributeRequestURI, DestAll) - jsonx.AppendString(buf, uri) - buf.WriteByte(',') - jsonx.AppendInt(buf, int64(makeSlowQueryID(slow.ParameterizedQuery))) - buf.WriteByte(',') - jsonx.AppendString(buf, slow.ParameterizedQuery) - buf.WriteByte(',') - jsonx.AppendString(buf, slow.DatastoreMetric) - buf.WriteByte(',') - jsonx.AppendInt(buf, int64(slow.Count)) - buf.WriteByte(',') - jsonx.AppendFloat(buf, slow.Total.Seconds()*1000.0) - buf.WriteByte(',') - jsonx.AppendFloat(buf, slow.Min.Seconds()*1000.0) - buf.WriteByte(',') - jsonx.AppendFloat(buf, slow.Duration.Seconds()*1000.0) - buf.WriteByte(',') - w := jsonFieldsWriter{buf: buf} - buf.WriteByte('{') - if "" != slow.Host { - w.stringField("host", slow.Host) - } - if "" != slow.PortPathOrID { - w.stringField("port_path_or_id", slow.PortPathOrID) - } - if "" != slow.DatabaseName { - w.stringField("database_name", slow.DatabaseName) - } - if nil != slow.StackTrace { - w.writerField("backtrace", slow.StackTrace) - } - if nil != slow.QueryParameters { - w.writerField("query_parameters", slow.QueryParameters) - } - - sharedBetterCATIntrinsics(&slow.TxnEvent, &w) - - buf.WriteByte('}') - buf.WriteByte(']') -} - -// WriteJSON marshals the collection of slow queries into JSON according to the -// schema expected by the collector. -// -// Note: This JSON does not contain the agentRunID. This is for unknown -// historical reasons. Since the agentRunID is included in the url, -// its use in the other commands' JSON is redundant (although required). -func (slows *slowQueries) WriteJSON(buf *bytes.Buffer) { - buf.WriteByte('[') - buf.WriteByte('[') - for idx, s := range slows.priorityQueue { - if idx > 0 { - buf.WriteByte(',') - } - s.WriteJSON(buf) - } - buf.WriteByte(']') - buf.WriteByte(']') -} - -func (slows *slowQueries) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { - if 0 == len(slows.priorityQueue) { - return nil, nil - } - estimate := 1024 * len(slows.priorityQueue) - buf := bytes.NewBuffer(make([]byte, 0, estimate)) - slows.WriteJSON(buf) - return buf.Bytes(), nil -} - -func (slows *slowQueries) MergeIntoHarvest(newHarvest *Harvest) { -} - -func (slows *slowQueries) EndpointMethod() string { - return cmdSlowSQLs -} diff --git a/internal/slow_queries_test.go b/internal/slow_queries_test.go deleted file mode 100644 index d471ed47b..000000000 --- a/internal/slow_queries_test.go +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "math/rand" - "strconv" - "strings" - "testing" - "time" -) - -func TestEmptySlowQueriesData(t *testing.T) { - slows := newSlowQueries(maxHarvestSlowSQLs) - js, err := slows.Data("agentRunID", time.Now()) - if nil != js || nil != err { - t.Error(string(js), err) - } -} - -func TestSlowQueriesBasic(t *testing.T) { - acfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/zip/zap", nil) - txnEvent := TxnEvent{ - FinalName: "WebTransaction/Go/hello", - Duration: 3 * time.Second, - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: false, - }, - } - - txnSlows := newSlowQueries(maxTxnSlowQueries) - qParams, err := vetQueryParameters(map[string]interface{}{ - strings.Repeat("X", attributeKeyLengthLimit+1): "invalid-key", - "invalid-value": struct{}{}, - "valid": 123, - }) - if nil == err { - t.Error("expected error") - } - txnSlows.observeInstance(slowQueryInstance{ - Duration: 2 * time.Second, - DatastoreMetric: "Datastore/statement/MySQL/users/INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - Host: "db-server-1", - PortPathOrID: "3306", - DatabaseName: "production", - StackTrace: nil, - QueryParameters: qParams, - }) - harvestSlows := newSlowQueries(maxHarvestSlowSQLs) - harvestSlows.Merge(txnSlows, txnEvent) - js, err := harvestSlows.Data("agentRunID", time.Now()) - expect := CompactJSONString(`[[ - [ - "WebTransaction/Go/hello", - "/zip/zap", - 3722056893, - "INSERT INTO users (name, age) VALUES ($1, $2)", - "Datastore/statement/MySQL/users/INSERT", - 1, - 2000, - 2000, - 2000, - { - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"production", - "query_parameters":{ - "valid":123 - } - } - ] -]]`) - if nil != err { - t.Error(err) - } - if string(js) != expect { - t.Error(string(js), expect) - } -} - -func TestSlowQueriesExcludeURI(t *testing.T) { - c := sampleAttributeConfigInput - c.Attributes.Exclude = []string{"request.uri"} - acfg := CreateAttributeConfig(c, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/zip/zap", nil) - txnEvent := TxnEvent{ - FinalName: "WebTransaction/Go/hello", - Duration: 3 * time.Second, - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: false, - }, - } - txnSlows := newSlowQueries(maxTxnSlowQueries) - qParams, err := vetQueryParameters(map[string]interface{}{ - strings.Repeat("X", attributeKeyLengthLimit+1): "invalid-key", - "invalid-value": struct{}{}, - "valid": 123, - }) - if nil == err { - t.Error("expected error") - } - txnSlows.observeInstance(slowQueryInstance{ - Duration: 2 * time.Second, - DatastoreMetric: "Datastore/statement/MySQL/users/INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - Host: "db-server-1", - PortPathOrID: "3306", - DatabaseName: "production", - StackTrace: nil, - QueryParameters: qParams, - }) - harvestSlows := newSlowQueries(maxHarvestSlowSQLs) - harvestSlows.Merge(txnSlows, txnEvent) - js, err := harvestSlows.Data("agentRunID", time.Now()) - expect := CompactJSONString(`[[ - [ - "WebTransaction/Go/hello", - "", - 3722056893, - "INSERT INTO users (name, age) VALUES ($1, $2)", - "Datastore/statement/MySQL/users/INSERT", - 1, - 2000, - 2000, - 2000, - { - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"production", - "query_parameters":{ - "valid":123 - } - } - ] -]]`) - if nil != err { - t.Error(err) - } - if string(js) != expect { - t.Error(string(js), expect) - } -} - -func TestSlowQueriesAggregation(t *testing.T) { - max := 50 - slows := make([]slowQueryInstance, 3*max) - for i := 0; i < max; i++ { - num := i + 1 - str := strconv.Itoa(num) - duration := time.Duration(num) * time.Second - slow := slowQueryInstance{ - DatastoreMetric: "Datastore/" + str, - ParameterizedQuery: str, - } - slow.Duration = duration - slow.TxnEvent = TxnEvent{ - FinalName: "Txn/0" + str, - } - slows[i*3+0] = slow - slow.Duration = duration + (100 * time.Second) - slow.TxnEvent = TxnEvent{ - FinalName: "Txn/1" + str, - } - slows[i*3+1] = slow - slow.Duration = duration + (200 * time.Second) - slow.TxnEvent = TxnEvent{ - FinalName: "Txn/2" + str, - } - slows[i*3+2] = slow - } - sq := newSlowQueries(10) - seed := int64(99) // arbitrary fixed seed - r := rand.New(rand.NewSource(seed)) - perm := r.Perm(max * 3) - for _, idx := range perm { - sq.observeInstance(slows[idx]) - } - js, err := sq.Data("agentRunID", time.Now()) - expect := CompactJSONString(`[[ - ["Txn/241","",2296612630,"41","Datastore/41",1,241000,241000,241000,{}], - ["Txn/242","",2279835011,"42","Datastore/42",2,384000,142000,242000,{}], - ["Txn/243","",2263057392,"43","Datastore/43",2,386000,143000,243000,{}], - ["Txn/244","",2380500725,"44","Datastore/44",3,432000,44000,244000,{}], - ["Txn/247","",2330167868,"47","Datastore/47",2,394000,147000,247000,{}], - ["Txn/245","",2363723106,"45","Datastore/45",2,290000,45000,245000,{}], - ["Txn/250","",2212577440,"50","Datastore/50",1,250000,250000,250000,{}], - ["Txn/246","",2346945487,"46","Datastore/46",2,392000,146000,246000,{}], - ["Txn/249","",2430833582,"49","Datastore/49",3,447000,49000,249000,{}], - ["Txn/248","",2447611201,"48","Datastore/48",3,444000,48000,248000,{}] -]]`) - if nil != err { - t.Error(err) - } - if string(js) != expect { - t.Error(string(js), expect) - } -} - -func TestSlowQueriesBetterCAT(t *testing.T) { - acfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/zip/zap", nil) - txnEvent := TxnEvent{ - FinalName: "WebTransaction/Go/hello", - Duration: 3 * time.Second, - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - } - - txnEvent.BetterCAT.Inbound = &Payload{ - payloadCaller: payloadCaller{ - TransportType: "HTTP", - Type: "Browser", - App: "caller-app", - Account: "caller-account", - }, - ID: "caller-id", - TransactionID: "caller-parent-id", - TracedID: "trace-id", - TransportDuration: 2 * time.Second, - } - - txnSlows := newSlowQueries(maxTxnSlowQueries) - qParams, err := vetQueryParameters(map[string]interface{}{ - strings.Repeat("X", attributeKeyLengthLimit+1): "invalid-key", - "invalid-value": struct{}{}, - "valid": 123, - }) - if nil == err { - t.Error("expected error") - } - txnSlows.observeInstance(slowQueryInstance{ - Duration: 2 * time.Second, - DatastoreMetric: "Datastore/statement/MySQL/users/INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - Host: "db-server-1", - PortPathOrID: "3306", - DatabaseName: "production", - StackTrace: nil, - QueryParameters: qParams, - }) - harvestSlows := newSlowQueries(maxHarvestSlowSQLs) - harvestSlows.Merge(txnSlows, txnEvent) - js, err := harvestSlows.Data("agentRunID", time.Now()) - expect := CompactJSONString(`[[ - [ - "WebTransaction/Go/hello", - "/zip/zap", - 3722056893, - "INSERT INTO users (name, age) VALUES ($1, $2)", - "Datastore/statement/MySQL/users/INSERT", - 1, - 2000, - 2000, - 2000, - { - "host":"db-server-1", - "port_path_or_id":"3306", - "database_name":"production", - "query_parameters":{"valid":123}, - "parent.type": "Browser", - "parent.app": "caller-app", - "parent.account": "caller-account", - "parent.transportType": "HTTP", - "parent.transportDuration": 2, - "guid":"txn-id", - "traceId":"trace-id", - "priority":0.500000, - "sampled":false - } - ] -]]`) - if nil != err { - t.Error(err) - } - if string(js) != expect { - t.Error(string(js), expect) - } -} diff --git a/internal/span_events.go b/internal/span_events.go deleted file mode 100644 index ee0e4bafc..000000000 --- a/internal/span_events.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "time" -) - -// https://source.datanerd.us/agents/agent-specs/blob/master/Span-Events.md - -type spanCategory string - -const ( - spanCategoryHTTP spanCategory = "http" - spanCategoryDatastore = "datastore" - spanCategoryGeneric = "generic" -) - -// SpanEvent represents a span event, necessary to support Distributed Tracing. -type SpanEvent struct { - TraceID string - GUID string - ParentID string - TransactionID string - Sampled bool - Priority Priority - Timestamp time.Time - Duration time.Duration - Name string - Category spanCategory - Component string - Kind string - IsEntrypoint bool - Attributes spanAttributeMap -} - -// WriteJSON prepares JSON in the format expected by the collector. -func (e *SpanEvent) WriteJSON(buf *bytes.Buffer) { - w := jsonFieldsWriter{buf: buf} - buf.WriteByte('[') - buf.WriteByte('{') - w.stringField("type", "Span") - w.stringField("traceId", e.TraceID) - w.stringField("guid", e.GUID) - if "" != e.ParentID { - w.stringField("parentId", e.ParentID) - } - w.stringField("transactionId", e.TransactionID) - w.boolField("sampled", e.Sampled) - w.writerField("priority", e.Priority) - w.intField("timestamp", e.Timestamp.UnixNano()/(1000*1000)) // in milliseconds - w.floatField("duration", e.Duration.Seconds()) - w.stringField("name", e.Name) - w.stringField("category", string(e.Category)) - if e.IsEntrypoint { - w.boolField("nr.entryPoint", true) - } - if e.Component != "" { - w.stringField("component", e.Component) - } - if e.Kind != "" { - w.stringField("span.kind", e.Kind) - } - buf.WriteByte('}') - buf.WriteByte(',') - buf.WriteByte('{') - // user attributes section is unused - buf.WriteByte('}') - buf.WriteByte(',') - buf.WriteByte('{') - - w = jsonFieldsWriter{buf: buf} - for key, val := range e.Attributes { - w.writerField(key.String(), val) - } - - buf.WriteByte('}') - buf.WriteByte(']') -} - -// MarshalJSON is used for testing. -func (e *SpanEvent) MarshalJSON() ([]byte, error) { - buf := bytes.NewBuffer(make([]byte, 0, 256)) - - e.WriteJSON(buf) - - return buf.Bytes(), nil -} - -type spanEvents struct { - *analyticsEvents -} - -func newSpanEvents(max int) *spanEvents { - return &spanEvents{ - analyticsEvents: newAnalyticsEvents(max), - } -} - -func (events *spanEvents) addEvent(e *SpanEvent, cat *BetterCAT) { - e.TraceID = cat.TraceID() - e.TransactionID = cat.ID - e.Sampled = cat.Sampled - e.Priority = cat.Priority - events.addEventPopulated(e) -} - -func (events *spanEvents) addEventPopulated(e *SpanEvent) { - events.analyticsEvents.addEvent(analyticsEvent{priority: e.Priority, jsonWriter: e}) -} - -// MergeFromTransaction merges the span events from a transaction into the -// harvest's span events. This should only be called if the transaction was -// sampled and span events are enabled. -func (events *spanEvents) MergeFromTransaction(txndata *TxnData) { - root := &SpanEvent{ - GUID: txndata.getRootSpanID(), - Timestamp: txndata.Start, - Duration: txndata.Duration, - Name: txndata.FinalName, - Category: spanCategoryGeneric, - IsEntrypoint: true, - } - if nil != txndata.BetterCAT.Inbound { - root.ParentID = txndata.BetterCAT.Inbound.ID - } - events.addEvent(root, &txndata.BetterCAT) - - for _, evt := range txndata.spanEvents { - events.addEvent(evt, &txndata.BetterCAT) - } -} - -func (events *spanEvents) MergeIntoHarvest(h *Harvest) { - h.SpanEvents.mergeFailed(events.analyticsEvents) -} - -func (events *spanEvents) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { - return events.CollectorJSON(agentRunID) -} - -func (events *spanEvents) EndpointMethod() string { - return cmdSpanEvents -} diff --git a/internal/span_events_test.go b/internal/span_events_test.go deleted file mode 100644 index d108994b9..000000000 --- a/internal/span_events_test.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "testing" - "time" -) - -func testSpanEventJSON(t *testing.T, e *SpanEvent, expect string) { - js, err := json.Marshal(e) - if nil != err { - t.Error(err) - return - } - expect = CompactJSONString(expect) - if string(js) != expect { - t.Errorf("\nexpect=%s\nactual=%s\n", expect, string(js)) - } -} - -var ( - sampleSpanEvent = SpanEvent{ - TraceID: "trace-id", - GUID: "guid", - TransactionID: "txn-id", - Sampled: true, - Priority: 0.5, - Timestamp: timeFromUnixMilliseconds(1488393111000), - Duration: 2 * time.Second, - Name: "myName", - Category: spanCategoryGeneric, - IsEntrypoint: true, - } -) - -func TestSpanEventGenericRootMarshal(t *testing.T) { - e := sampleSpanEvent - testSpanEventJSON(t, &e, `[ - { - "type":"Span", - "traceId":"trace-id", - "guid":"guid", - "transactionId":"txn-id", - "sampled":true, - "priority":0.500000, - "timestamp":1488393111000, - "duration":2, - "name":"myName", - "category":"generic", - "nr.entryPoint":true - }, - {}, - {}]`) -} - -func TestSpanEventDatastoreMarshal(t *testing.T) { - e := sampleSpanEvent - - // Alter sample span event for this test case - e.IsEntrypoint = false - e.ParentID = "parent-id" - e.Category = spanCategoryDatastore - e.Kind = "client" - e.Component = "mySql" - e.Attributes.addString(spanAttributeDBStatement, "SELECT * from foo") - e.Attributes.addString(spanAttributeDBInstance, "123") - e.Attributes.addString(spanAttributePeerAddress, "{host}:{portPathOrId}") - e.Attributes.addString(spanAttributePeerHostname, "host") - - expectEvent(t, &e, WantEvent{ - Intrinsics: map[string]interface{}{ - "type": "Span", - "traceId": "trace-id", - "guid": "guid", - "parentId": "parent-id", - "transactionId": "txn-id", - "sampled": true, - "priority": 0.500000, - "timestamp": 1.488393111e+12, - "duration": 2, - "name": "myName", - "category": "datastore", - "component": "mySql", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "db.statement": "SELECT * from foo", - "db.instance": "123", - "peer.address": "{host}:{portPathOrId}", - "peer.hostname": "host", - }, - }) -} - -func TestSpanEventDatastoreWithoutHostMarshal(t *testing.T) { - e := sampleSpanEvent - - // Alter sample span event for this test case - e.IsEntrypoint = false - e.ParentID = "parent-id" - e.Category = spanCategoryDatastore - e.Kind = "client" - e.Component = "mySql" - e.Attributes.addString(spanAttributeDBStatement, "SELECT * from foo") - e.Attributes.addString(spanAttributeDBInstance, "123") - - // According to CHANGELOG.md, as of version 1.5, if `Host` and - // `PortPathOrID` are not provided in a Datastore segment, they - // do not appear as `"unknown"` in transaction traces and slow - // query traces. To maintain parity with the other offerings of - // the Go Agent, neither do Span Events. - expectEvent(t, &e, WantEvent{ - Intrinsics: map[string]interface{}{ - "type": "Span", - "traceId": "trace-id", - "guid": "guid", - "parentId": "parent-id", - "transactionId": "txn-id", - "sampled": true, - "priority": 0.500000, - "timestamp": 1.488393111e+12, - "duration": 2, - "name": "myName", - "category": "datastore", - "component": "mySql", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "db.statement": "SELECT * from foo", - "db.instance": "123", - }, - }) -} - -func TestSpanEventExternalMarshal(t *testing.T) { - e := sampleSpanEvent - - // Alter sample span event for this test case - e.ParentID = "parent-id" - e.IsEntrypoint = false - e.Category = spanCategoryHTTP - e.Kind = "client" - e.Component = "http" - e.Attributes.addString(spanAttributeHTTPURL, "http://url.com") - e.Attributes.addString(spanAttributeHTTPMethod, "GET") - - expectEvent(t, &e, WantEvent{ - Intrinsics: map[string]interface{}{ - "type": "Span", - "traceId": "trace-id", - "guid": "guid", - "parentId": "parent-id", - "transactionId": "txn-id", - "sampled": true, - "priority": 0.500000, - "timestamp": 1.488393111e+12, - "duration": 2, - "name": "myName", - "category": "http", - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "http.url": "http://url.com", - "http.method": "GET", - }, - }) -} - -func TestSpanEventsEndpointMethod(t *testing.T) { - events := &spanEvents{} - m := events.EndpointMethod() - if m != cmdSpanEvents { - t.Error(m) - } -} - -func TestSpanEventsMergeFromTransaction(t *testing.T) { - args := &TxnData{} - args.Start = time.Now() - args.Duration = 1 * time.Second - args.FinalName = "finalName" - args.BetterCAT.Sampled = true - args.BetterCAT.Priority = 0.7 - args.BetterCAT.Enabled = true - args.BetterCAT.ID = "txn-id" - args.BetterCAT.Inbound = &Payload{ - ID: "inbound-id", - TracedID: "inbound-trace-id", - } - args.rootSpanID = "root-span-id" - - args.spanEvents = []*SpanEvent{ - { - GUID: "span-1-id", - ParentID: "root-span-id", - Timestamp: time.Now(), - Duration: 3 * time.Millisecond, - Name: "span1", - Category: spanCategoryGeneric, - IsEntrypoint: false, - }, - { - GUID: "span-2-id", - ParentID: "span-1-id", - Timestamp: time.Now(), - Duration: 3 * time.Millisecond, - Name: "span2", - Category: spanCategoryGeneric, - IsEntrypoint: false, - }, - } - - spanEvents := newSpanEvents(10) - spanEvents.MergeFromTransaction(args) - - ExpectSpanEvents(t, spanEvents, []WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "finalName", - "sampled": true, - "priority": 0.7, - "category": spanCategoryGeneric, - "parentId": "inbound-id", - "nr.entryPoint": true, - "guid": "root-span-id", - "transactionId": "txn-id", - "traceId": "inbound-trace-id", - }, - }, - { - Intrinsics: map[string]interface{}{ - "name": "span1", - "sampled": true, - "priority": 0.7, - "category": spanCategoryGeneric, - "parentId": "root-span-id", - "guid": "span-1-id", - "transactionId": "txn-id", - "traceId": "inbound-trace-id", - }, - }, - { - Intrinsics: map[string]interface{}{ - "name": "span2", - "sampled": true, - "priority": 0.7, - "category": spanCategoryGeneric, - "parentId": "span-1-id", - "guid": "span-2-id", - "transactionId": "txn-id", - "traceId": "inbound-trace-id", - }, - }, - }) -} diff --git a/internal/sqlparse/sqlparse.go b/internal/sqlparse/sqlparse.go deleted file mode 100644 index 3e57aab91..000000000 --- a/internal/sqlparse/sqlparse.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sqlparse - -import ( - "regexp" - "strings" - - newrelic "github.com/newrelic/go-agent" -) - -func extractTable(s string) string { - s = extractTableRegex.ReplaceAllString(s, "") - if idx := strings.Index(s, "."); idx > 0 { - s = s[idx+1:] - } - return s -} - -var ( - basicTable = `[^)(\]\[\}\{\s,;]+` - enclosedTable = `[\[\(\{]` + `\s*` + basicTable + `\s*` + `[\]\)\}]` - tablePattern = `(` + `\s+` + basicTable + `|` + `\s*` + enclosedTable + `)` - extractTableRegex = regexp.MustCompile(`[\s` + "`" + `"'\(\)\{\}\[\]]*`) - updateRegex = regexp.MustCompile(`(?is)^update(?:\s+(?:low_priority|ignore|or|rollback|abort|replace|fail|only))*` + tablePattern) - sqlOperations = map[string]*regexp.Regexp{ - "select": regexp.MustCompile(`(?is)^.*?\sfrom` + tablePattern), - "delete": regexp.MustCompile(`(?is)^.*?\sfrom` + tablePattern), - "insert": regexp.MustCompile(`(?is)^.*?\sinto?` + tablePattern), - "update": updateRegex, - "call": nil, - "create": nil, - "drop": nil, - "show": nil, - "set": nil, - "exec": nil, - "execute": nil, - "alter": nil, - "commit": nil, - "rollback": nil, - } - firstWordRegex = regexp.MustCompile(`^\w+`) - cCommentRegex = regexp.MustCompile(`(?is)/\*.*?\*/`) - lineCommentRegex = regexp.MustCompile(`(?im)(?:--|#).*?$`) - sqlPrefixRegex = regexp.MustCompile(`^[\s;]*`) -) - -// ParseQuery parses table and operation from the SQL query string. -func ParseQuery(segment *newrelic.DatastoreSegment, query string) { - s := cCommentRegex.ReplaceAllString(query, "") - s = lineCommentRegex.ReplaceAllString(s, "") - s = sqlPrefixRegex.ReplaceAllString(s, "") - op := strings.ToLower(firstWordRegex.FindString(s)) - if rg, ok := sqlOperations[op]; ok { - segment.Operation = op - if nil != rg { - if m := rg.FindStringSubmatch(s); len(m) > 1 { - segment.Collection = extractTable(m[1]) - } - } - } -} diff --git a/internal/sqlparse/sqlparse_test.go b/internal/sqlparse/sqlparse_test.go deleted file mode 100644 index b7ed76a70..000000000 --- a/internal/sqlparse/sqlparse_test.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sqlparse - -import ( - "testing" - - newrelic "github.com/newrelic/go-agent" - "github.com/newrelic/go-agent/internal/crossagent" -) - -type sqlTestcase struct { - Input string `json:"input"` - Operation string `json:"operation"` - Table string `json:"table"` -} - -func (tc sqlTestcase) test(t *testing.T) { - var segment newrelic.DatastoreSegment - ParseQuery(&segment, tc.Input) - if tc.Operation == "other" { - // Allow for matching of Operation "other" to "" - if segment.Operation != "" { - t.Errorf("operation mismatch query='%s' wanted='%s' got='%s'", - tc.Input, tc.Operation, segment.Operation) - } - } else if segment.Operation != tc.Operation { - t.Errorf("operation mismatch query='%s' wanted='%s' got='%s'", - tc.Input, tc.Operation, segment.Operation) - } - // The Go agent subquery behavior does not match the PHP Agent. - if tc.Table == "(subquery)" { - return - } - if tc.Table != segment.Collection { - t.Errorf("table mismatch query='%s' wanted='%s' got='%s'", - tc.Input, tc.Table, segment.Collection) - } -} - -func TestParseSQLCrossAgent(t *testing.T) { - var tcs []sqlTestcase - err := crossagent.ReadJSON("sql_parsing.json", &tcs) - if err != nil { - t.Fatal(err) - } - - for _, tc := range tcs { - tc.test(t) - } -} - -func TestParseSQLSubQuery(t *testing.T) { - for _, tc := range []sqlTestcase{ - {Input: "SELECT * FROM (SELECT * FROM foobar)", Operation: "select", Table: "foobar"}, - {Input: "SELECT * FROM (SELECT * FROM foobar) WHERE x > y", Operation: "select", Table: "foobar"}, - {Input: "SELECT * FROM(SELECT * FROM foobar) WHERE x > y", Operation: "select", Table: "foobar"}, - } { - tc.test(t) - } -} - -func TestParseSQLOther(t *testing.T) { - for _, tc := range []sqlTestcase{ - // Test that we handle table names enclosed in brackets. - {Input: "SELECT * FROM [foo]", Operation: "select", Table: "foo"}, - {Input: "SELECT * FROM[foo]", Operation: "select", Table: "foo"}, - {Input: "SELECT * FROM [ foo ]", Operation: "select", Table: "foo"}, - {Input: "SELECT * FROM [ 'foo' ]", Operation: "select", Table: "foo"}, - {Input: "SELECT * FROM[ `something`.'foo' ]", Operation: "select", Table: "foo"}, - // Test that we handle the cheese. - {Input: "SELECT fromage FROM fromagier", Operation: "select", Table: "fromagier"}, - } { - tc.test(t) - } -} - -func TestParseSQLUpdateExtraKeywords(t *testing.T) { - for _, tc := range []sqlTestcase{ - {Input: "update or rollback foo", Operation: "update", Table: "foo"}, - {Input: "update only foo", Operation: "update", Table: "foo"}, - {Input: "update low_priority ignore{foo}", Operation: "update", Table: "foo"}, - } { - tc.test(t) - } -} - -func TestLineComment(t *testing.T) { - for _, tc := range []sqlTestcase{ - { - Input: `SELECT -- * FROM tricky - * FROM foo`, - Operation: "select", - Table: "foo", - }, - { - Input: `SELECT # * FROM tricky - * FROM foo`, - Operation: "select", - Table: "foo", - }, - { - Input: ` -- SELECT * FROM tricky - SELECT * FROM foo`, - Operation: "select", - Table: "foo", - }, - { - Input: ` # SELECT * FROM tricky - SELECT * FROM foo`, - Operation: "select", - Table: "foo", - }, - { - Input: `SELECT * FROM -- tricky - foo`, - Operation: "select", - Table: "foo", - }, - } { - tc.test(t) - } -} - -func TestSemicolonPrefix(t *testing.T) { - for _, tc := range []sqlTestcase{ - { - Input: `;select * from foo`, - Operation: "select", - Table: "foo", - }, - { - Input: ` ;; ; select * from foo`, - Operation: "select", - Table: "foo", - }, - { - Input: ` ; - SELECT * FROM foo`, - Operation: "select", - Table: "foo", - }, - } { - tc.test(t) - } -} - -func TestDollarSignTable(t *testing.T) { - for _, tc := range []sqlTestcase{ - { - Input: `select * from $dollar_100_$`, - Operation: "select", - Table: "$dollar_100_$", - }, - } { - tc.test(t) - } -} - -func TestPriorityQuery(t *testing.T) { - // Test that we handle: - // https://dev.mysql.com/doc/refman/8.0/en/insert.html - // INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] [INTO] tbl_name - for _, tc := range []sqlTestcase{ - { - Input: `INSERT HIGH_PRIORITY INTO employee VALUES('Tom',12345,'Sales',100)`, - Operation: "insert", - Table: "employee", - }, - } { - tc.test(t) - } -} - -func TestExtractTable(t *testing.T) { - for idx, tc := range []string{ - "table", - "`table`", - `"table"`, - "`database.table`", - "`database`.table", - "database.`table`", - "`database`.`table`", - " { table }", - "\n[table]", - "\t ( 'database'.`table` ) ", - } { - table := extractTable(tc) - if table != "table" { - t.Error(idx, table) - } - } -} diff --git a/internal/stack_frame.go b/internal/stack_frame.go deleted file mode 100644 index 5087af003..000000000 --- a/internal/stack_frame.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.7 - -package internal - -import "runtime" - -func (st StackTrace) frames() []stacktraceFrame { - if len(st) == 0 { - return nil - } - frames := runtime.CallersFrames(st) // CallersFrames is only available in Go 1.7+ - fs := make([]stacktraceFrame, 0, maxStackTraceFrames) - var frame runtime.Frame - more := true - for more { - frame, more = frames.Next() - fs = append(fs, stacktraceFrame{ - Name: frame.Function, - File: frame.File, - Line: int64(frame.Line), - }) - } - return fs -} diff --git a/internal/stack_frame_pre_1_7.go b/internal/stack_frame_pre_1_7.go deleted file mode 100644 index ec13b255f..000000000 --- a/internal/stack_frame_pre_1_7.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build !go1.7 - -package internal - -import "runtime" - -func (st StackTrace) frames() []stacktraceFrame { - fs := make([]stacktraceFrame, len(st)) - for idx, pc := range st { - fs[idx] = lookupFrame(pc) - } - return fs -} - -func lookupFrame(pc uintptr) stacktraceFrame { - // The Golang runtime package documentation says "To look up the file - // and line number of the call itself, use pc[i]-1. As an exception to - // this rule, if pc[i-1] corresponds to the function runtime.sigpanic, - // then pc[i] is the program counter of a faulting instruction and - // should be used without any subtraction." - // - // TODO: Fully understand when this subtraction is necessary. - place := pc - 1 - f := runtime.FuncForPC(place) - if nil == f { - return stacktraceFrame{} - } - file, line := f.FileLine(place) - return stacktraceFrame{ - Name: f.Name(), - File: file, - Line: int64(line), - } -} diff --git a/internal/stacktrace.go b/internal/stacktrace.go deleted file mode 100644 index 67726d8f8..000000000 --- a/internal/stacktrace.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "path" - "runtime" - "strings" -) - -// StackTrace is a stack trace. -type StackTrace []uintptr - -// GetStackTrace returns a new StackTrace. -func GetStackTrace() StackTrace { - skip := 1 // skip runtime.Callers - callers := make([]uintptr, maxStackTraceFrames) - written := runtime.Callers(skip, callers) - return callers[:written] -} - -type stacktraceFrame struct { - Name string - File string - Line int64 -} - -func (f stacktraceFrame) formattedName() string { - if strings.HasPrefix(f.Name, "go.") { - // This indicates an anonymous struct. eg. - // "go.(*struct { github.com/newrelic/go-agent.threadWithExtras }).NoticeError" - return f.Name - } - return path.Base(f.Name) -} - -func (f stacktraceFrame) isAgent() bool { - // Note this is not a contains conditional rather than a prefix - // conditional to handle anonymous functions like: - // "go.(*struct { github.com/newrelic/go-agent.threadWithExtras }).NoticeError" - return strings.Contains(f.Name, "github.com/newrelic/go-agent/internal.") || - strings.Contains(f.Name, "github.com/newrelic/go-agent.") -} - -func (f stacktraceFrame) WriteJSON(buf *bytes.Buffer) { - buf.WriteByte('{') - w := jsonFieldsWriter{buf: buf} - if f.Name != "" { - w.stringField("name", f.formattedName()) - } - if f.File != "" { - w.stringField("filepath", f.File) - } - if f.Line != 0 { - w.intField("line", f.Line) - } - buf.WriteByte('}') -} - -func writeFrames(buf *bytes.Buffer, frames []stacktraceFrame) { - // Remove top agent frames. - for len(frames) > 0 && frames[0].isAgent() { - frames = frames[1:] - } - // Truncate excessively long stack traces (they may be provided by the - // customer). - if len(frames) > maxStackTraceFrames { - frames = frames[0:maxStackTraceFrames] - } - - buf.WriteByte('[') - for idx, frame := range frames { - if idx > 0 { - buf.WriteByte(',') - } - frame.WriteJSON(buf) - } - buf.WriteByte(']') -} - -// WriteJSON adds the stack trace to the buffer in the JSON form expected by the -// collector. -func (st StackTrace) WriteJSON(buf *bytes.Buffer) { - frames := st.frames() - writeFrames(buf, frames) -} - -// MarshalJSON prepares JSON in the format expected by the collector. -func (st StackTrace) MarshalJSON() ([]byte, error) { - estimate := 256 * len(st) - buf := bytes.NewBuffer(make([]byte, 0, estimate)) - - st.WriteJSON(buf) - - return buf.Bytes(), nil -} diff --git a/internal/stacktrace_test.go b/internal/stacktrace_test.go deleted file mode 100644 index 4802819da..000000000 --- a/internal/stacktrace_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "encoding/json" - "strings" - "testing" - - "github.com/newrelic/go-agent/internal/stacktracetest" -) - -func TestGetStackTrace(t *testing.T) { - stack := GetStackTrace() - js, err := json.Marshal(stack) - if nil != err { - t.Fatal(err) - } - if nil == js { - t.Fatal(string(js)) - } -} - -func TestLongStackTraceLimitsFrames(t *testing.T) { - st := stacktracetest.CountedCall(maxStackTraceFrames+20, func() []uintptr { - return GetStackTrace() - }) - if len(st) != maxStackTraceFrames { - t.Error("Unexpected size of stacktrace", maxStackTraceFrames, len(st)) - } - l := len(StackTrace(st).frames()) - if l != maxStackTraceFrames { - t.Error("Unexpected number of frames", maxStackTraceFrames, l) - } -} - -func TestManyStackTraceFramesLimitsOutput(t *testing.T) { - frames := make([]stacktraceFrame, maxStackTraceFrames+20) - expect := `[ - {},{},{},{},{},{},{},{},{},{}, - {},{},{},{},{},{},{},{},{},{}, - {},{},{},{},{},{},{},{},{},{}, - {},{},{},{},{},{},{},{},{},{}, - {},{},{},{},{},{},{},{},{},{}, - {},{},{},{},{},{},{},{},{},{}, - {},{},{},{},{},{},{},{},{},{}, - {},{},{},{},{},{},{},{},{},{}, - {},{},{},{},{},{},{},{},{},{}, - {},{},{},{},{},{},{},{},{},{} - ]` - estimate := 256 * len(frames) - output := bytes.NewBuffer(make([]byte, 0, estimate)) - writeFrames(output, frames) - if CompactJSONString(expect) != output.String() { - t.Error("Unexpected JSON output", CompactJSONString(expect), output.String()) - } -} - -func TestStacktraceFrames(t *testing.T) { - // This stacktrace taken from Go 1.11 - inputFrames := []stacktraceFrame{ - { - File: "/Users/will/Desktop/gopath/src/github.com/newrelic/go-agent/internal/stacktrace.go", - Name: "github.com/newrelic/go-agent/internal.GetStackTrace", - Line: 17, - }, - { - File: "/Users/will/Desktop/gopath/src/github.com/newrelic/go-agent/internal_txn.go", - Name: "github.com/newrelic/go-agent.(*txn).NoticeError", - Line: 696, - }, - { - File: "\u003cautogenerated\u003e", - Name: "go.(*struct { github.com/newrelic/go-agent.threadWithExtras }).NoticeError", - Line: 1, - }, - { - File: "/Users/will/Desktop/gopath/src/github.com/newrelic/go-agent/internal_attributes_test.go", - Name: "github.com/newrelic/go-agent.TestAddAttributeSecurityPolicyDisablesInclude", - Line: 68, - }, - { - File: "/Users/will/.gvm/gos/go1.11/src/testing/testing.go", - Name: "testing.tRunner", - Line: 827, - }, - { - File: "/Users/will/.gvm/gos/go1.11/src/runtime/asm_amd64.s", - Name: "runtime.goexit", - Line: 1333, - }, - } - buf := &bytes.Buffer{} - writeFrames(buf, inputFrames) - expectedJSON := `[ - { - "name":"testing.tRunner", - "filepath":"/Users/will/.gvm/gos/go1.11/src/testing/testing.go", - "line":827 - }, - { - "name":"runtime.goexit", - "filepath":"/Users/will/.gvm/gos/go1.11/src/runtime/asm_amd64.s", - "line":1333 - } - ]` - testExpectedJSON(t, expectedJSON, buf.String()) -} - -func TestStackTraceTopFrame(t *testing.T) { - // This test uses a separate package since the stacktrace code removes - // the top stack frames which are in packages "newrelic" and "internal". - stackJSON := stacktracetest.TopStackFrame(func() []byte { - st := GetStackTrace() - js, _ := json.Marshal(st) - return js - }) - - stack := []struct { - Name string `json:"name"` - FilePath string `json:"filepath"` - Line int `json:"line"` - }{} - if err := json.Unmarshal(stackJSON, &stack); err != nil { - t.Fatal(err) - } - if len(stack) < 2 { - t.Fatal(string(stackJSON)) - } - if stack[0].Name != "stacktracetest.TopStackFrame" { - t.Error(string(stackJSON)) - } - if stack[0].Line != 9 { - t.Error(string(stackJSON)) - } - if !strings.Contains(stack[0].FilePath, "go-agent/internal/stacktracetest/stacktracetest.go") { - t.Error(string(stackJSON)) - } -} - -func TestFramesCount(t *testing.T) { - st := stacktracetest.CountedCall(3, func() []uintptr { - return GetStackTrace() - }) - frames := StackTrace(st).frames() - if len(st) != len(frames) { - t.Error("Invalid # of frames", len(st), len(frames)) - } -} diff --git a/internal/stacktracetest/stacktracetest.go b/internal/stacktracetest/stacktracetest.go deleted file mode 100644 index 57d2071f0..000000000 --- a/internal/stacktracetest/stacktracetest.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package stacktracetest helps test stack trace behavior. -package stacktracetest - -// TopStackFrame is a function should will appear in the stacktrace. -func TopStackFrame(generateStacktrace func() []byte) []byte { - return generateStacktrace() -} - -// CountedCall is a function that allows you to generate a stack trace with this function being called a particular -// number of times. The parameter f should be a function that returns a StackTrace (but it is referred to as []uintptr -// in order to not create a circular dependency on the internal package) -func CountedCall(i int, f func() []uintptr) []uintptr { - if i > 0 { - return CountedCall(i-1, f) - } - return f() -} diff --git a/internal/synthetics_test.go b/internal/synthetics_test.go deleted file mode 100644 index 91d1d84b8..000000000 --- a/internal/synthetics_test.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/newrelic/go-agent/internal/cat" - "github.com/newrelic/go-agent/internal/crossagent" -) - -type harvestedTxnTrace struct { - startTimeMs float64 - durationToResponse float64 - transactionName string - requestURL string - traceDetails struct { - attributes struct { - agentAttributes eventAttributes - userAttributes eventAttributes - intrinsics eventAttributes - } - } - catGUID string - forcePersistFlag bool - xraySessionID string - syntheticsResourceID string -} - -func (h *harvestedTxnTrace) UnmarshalJSON(data []byte) error { - var arr []interface{} - - if err := json.Unmarshal(data, &arr); err != nil { - return err - } - - if len(arr) != 10 { - return fmt.Errorf("unexpected number of transaction trace items: %d", len(arr)) - } - - h.startTimeMs = arr[0].(float64) - h.durationToResponse = arr[1].(float64) - h.transactionName = arr[2].(string) - if nil != arr[3] { - h.requestURL = arr[3].(string) - } - // Item 4 -- the trace -- will be dealt with shortly. - h.catGUID = arr[5].(string) - // Item 6 intentionally ignored. - h.forcePersistFlag = arr[7].(bool) - if arr[8] != nil { - h.xraySessionID = arr[8].(string) - } - h.syntheticsResourceID = arr[9].(string) - - traceDetails := arr[4].([]interface{}) - attributes := traceDetails[4].(map[string]interface{}) - - h.traceDetails.attributes.agentAttributes = attributes["agentAttributes"].(map[string]interface{}) - h.traceDetails.attributes.userAttributes = attributes["userAttributes"].(map[string]interface{}) - h.traceDetails.attributes.intrinsics = attributes["intrinsics"].(map[string]interface{}) - - return nil -} - -func harvestTxnDataTrace(t *TxnData) (*harvestedTxnTrace, error) { - // Since transaction trace JSON is built using string manipulation, we have - // to do an awkward marshal/unmarshal shuffle to be able to verify the - // intrinsics. - ht := HarvestTrace{ - TxnEvent: t.TxnEvent, - Trace: t.TxnTrace, - } - js, err := ht.MarshalJSON() - if err != nil { - return nil, err - } - - trace := &harvestedTxnTrace{} - if err := json.Unmarshal(js, trace); err != nil { - return nil, err - } - - return trace, nil -} - -func TestSynthetics(t *testing.T) { - var testcases []struct { - Name string `json:"name"` - Settings struct { - AgentEncodingKey string `json:"agentEncodingKey"` - SyntheticsEncodingKey string `json:"syntheticsEncodingKey"` - TransactionGUID string `json:"transactionGuid"` - TrustedAccountIDs []int `json:"trustedAccountIds"` - } `json:"settings"` - InputHeaderPayload json.RawMessage `json:"inputHeaderPayload"` - InputObfuscatedHeader map[string]string `json:"inputObfuscatedHeader"` - OutputTransactionTrace struct { - Header struct { - Field9 string `json:"field_9"` - } `json:"header"` - ExpectedIntrinsics map[string]string `json:"expectedIntrinsics"` - NonExpectedIntrinsics []string `json:"nonExpectedIntrinsics"` - } `json:"outputTransactionTrace"` - OutputTransactionEvent struct { - ExpectedAttributes map[string]string `json:"expectedAttributes"` - NonExpectedAttributes []string `json:"nonExpectedAttributes"` - } `json:"outputTransactionEvent"` - OutputExternalRequestHeader struct { - ExpectedHeader map[string]string `json:"expectedHeader"` - NonExpectedHeader []string `json:"nonExpectedHeader"` - } `json:"outputExternalRequestHeader"` - } - - err := crossagent.ReadJSON("synthetics/synthetics.json", &testcases) - if err != nil { - t.Fatal(err) - } - - for _, tc := range testcases { - // Fake enough transaction data to run the test. - tr := &TxnData{ - Name: "txn", - } - - tr.CrossProcess.Init(false, false, &ConnectReply{ - CrossProcessID: "1#1", - TrustedAccounts: make(map[int]struct{}), - EncodingKey: tc.Settings.AgentEncodingKey, - }) - - // Set up the trusted accounts. - for _, account := range tc.Settings.TrustedAccountIDs { - tr.CrossProcess.TrustedAccounts[account] = struct{}{} - } - - // Set up the GUID. - if tc.Settings.TransactionGUID != "" { - tr.CrossProcess.GUID = tc.Settings.TransactionGUID - } - - // Parse the input header, ignoring any errors. - inputHeaders := make(http.Header) - for k, v := range tc.InputObfuscatedHeader { - inputHeaders.Add(k, v) - } - - tr.CrossProcess.handleInboundRequestEncodedSynthetics(inputHeaders.Get(cat.NewRelicSyntheticsName)) - - // Get the headers for an external request. - metadata, err := tr.CrossProcess.CreateCrossProcessMetadata("txn", "app") - if err != nil { - t.Fatalf("%s: error creating outbound request headers: %v", tc.Name, err) - } - - // Verify that the header either exists or doesn't exist, depending on the - // test case. - headers := MetadataToHTTPHeader(metadata) - for key, value := range tc.OutputExternalRequestHeader.ExpectedHeader { - obfuscated := headers.Get(key) - if obfuscated == "" { - t.Errorf("%s: expected output header %s not found", tc.Name, key) - } else if value != obfuscated { - t.Errorf("%s: expected output header %s mismatch: expected=%s; got=%s", tc.Name, key, value, obfuscated) - } - } - - for _, key := range tc.OutputExternalRequestHeader.NonExpectedHeader { - if value := headers.Get(key); value != "" { - t.Errorf("%s: output header %s expected to be missing; got %s", tc.Name, key, value) - } - } - - // Harvest the trace. - trace, err := harvestTxnDataTrace(tr) - if err != nil { - t.Errorf("%s: error harvesting trace data: %v", tc.Name, err) - } - - // Check the synthetics resource ID. - if trace.syntheticsResourceID != tc.OutputTransactionTrace.Header.Field9 { - t.Errorf("%s: unexpected field 9: expected=%s; got=%s", tc.Name, tc.OutputTransactionTrace.Header.Field9, trace.syntheticsResourceID) - } - - // Check for expected intrinsics. - for key, value := range tc.OutputTransactionTrace.ExpectedIntrinsics { - // First, check if the key exists at all. - if !trace.traceDetails.attributes.intrinsics.has(key) { - t.Fatalf("%s: missing intrinsic %s", tc.Name, key) - } - - // Everything we're looking for is a string, so we can be a little lazy - // here. - if err := trace.traceDetails.attributes.intrinsics.isString(key, value); err != nil { - t.Errorf("%s: %v", tc.Name, err) - } - } - - // Now we verify that the unexpected intrinsics didn't miraculously appear. - for _, key := range tc.OutputTransactionTrace.NonExpectedIntrinsics { - if trace.traceDetails.attributes.intrinsics.has(key) { - t.Errorf("%s: expected intrinsic %s to be missing; instead, got value %v", tc.Name, key, trace.traceDetails.attributes.intrinsics[key]) - } - } - - // Harvest the event. - event, err := harvestTxnDataEvent(tr) - if err != nil { - t.Errorf("%s: error harvesting event data: %v", tc.Name, err) - } - - // Now we have the event, let's look for the expected intrinsics. - for key, value := range tc.OutputTransactionEvent.ExpectedAttributes { - // First, check if the key exists at all. - if !event.intrinsics.has(key) { - t.Fatalf("%s: missing intrinsic %s", tc.Name, key) - } - - // Everything we're looking for is a string, so we can be a little lazy - // here. - if err := event.intrinsics.isString(key, value); err != nil { - t.Errorf("%s: %v", tc.Name, err) - } - } - - // Now we verify that the unexpected intrinsics didn't miraculously appear. - for _, key := range tc.OutputTransactionEvent.NonExpectedAttributes { - if event.intrinsics.has(key) { - t.Errorf("%s: expected intrinsic %s to be missing; instead, got value %v", tc.Name, key, event.intrinsics[key]) - } - } - } -} diff --git a/internal/sysinfo/bootid.go b/internal/sysinfo/bootid.go deleted file mode 100644 index bd90c8275..000000000 --- a/internal/sysinfo/bootid.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "bytes" - "fmt" - "io/ioutil" - "runtime" -) - -// BootID returns the boot ID of the executing kernel. -func BootID() (string, error) { - if "linux" != runtime.GOOS { - return "", ErrFeatureUnsupported - } - data, err := ioutil.ReadFile("/proc/sys/kernel/random/boot_id") - if err != nil { - return "", err - } - - return validateBootID(data) -} - -type invalidBootID string - -func (e invalidBootID) Error() string { - return fmt.Sprintf("Boot id has unrecognized format, id=%q", string(e)) -} - -func isASCIIByte(b byte) bool { - return (b >= 0x20 && b <= 0x7f) -} - -func validateBootID(data []byte) (string, error) { - // We're going to go for the permissive reading of - // https://source.datanerd.us/agents/agent-specs/blob/master/Utilization.md: - // any ASCII (excluding control characters, because I'm pretty sure that's not - // in the spirit of the spec) string will be sent up to and including 128 - // bytes in length. - trunc := bytes.TrimSpace(data) - if len(trunc) > 128 { - trunc = trunc[:128] - } - for _, b := range trunc { - if !isASCIIByte(b) { - return "", invalidBootID(data) - } - } - - return string(trunc), nil -} diff --git a/internal/sysinfo/docker.go b/internal/sysinfo/docker.go deleted file mode 100644 index 97deee896..000000000 --- a/internal/sysinfo/docker.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "os" - "regexp" - "runtime" -) - -var ( - // ErrDockerNotFound is returned if a Docker ID is not found in - // /proc/self/cgroup - ErrDockerNotFound = errors.New("Docker ID not found") -) - -// DockerID attempts to detect Docker. -func DockerID() (string, error) { - if "linux" != runtime.GOOS { - return "", ErrFeatureUnsupported - } - - f, err := os.Open("/proc/self/cgroup") - if err != nil { - return "", err - } - defer f.Close() - - return parseDockerID(f) -} - -var ( - // The DockerID must be a 64-character lowercase hex string - // be greedy and match anything 64-characters or longer to spot invalid IDs - dockerIDLength = 64 - dockerIDRegexRaw = fmt.Sprintf("[0-9a-f]{%d,}", dockerIDLength) - dockerIDRegex = regexp.MustCompile(dockerIDRegexRaw) -) - -func parseDockerID(r io.Reader) (string, error) { - // Each line in the cgroup file consists of three colon delimited fields. - // 1. hierarchy ID - we don't care about this - // 2. subsystems - comma separated list of cgroup subsystem names - // 3. control group - control group to which the process belongs - // - // Example - // 5:cpuacct,cpu,cpuset:/daemons - - var id string - - for scanner := bufio.NewScanner(r); scanner.Scan(); { - line := scanner.Bytes() - cols := bytes.SplitN(line, []byte(":"), 3) - - if len(cols) < 3 { - continue - } - - // We're only interested in the cpu subsystem. - if !isCPUCol(cols[1]) { - continue - } - - id = dockerIDRegex.FindString(string(cols[2])) - - if err := validateDockerID(id); err != nil { - // We can stop searching at this point, the CPU - // subsystem should only occur once, and its cgroup is - // not docker or not a format we accept. - return "", err - } - return id, nil - } - - return "", ErrDockerNotFound -} - -func isCPUCol(col []byte) bool { - // Sometimes we have multiple subsystems in one line, as in this example - // from: - // https://source.datanerd.us/newrelic/cross_agent_tests/blob/master/docker_container_id/docker-1.1.2-native-driver-systemd.txt - // - // 3:cpuacct,cpu:/system.slice/docker-67f98c9e6188f9c1818672a15dbe46237b6ee7e77f834d40d41c5fb3c2f84a2f.scope - splitCSV := func(r rune) bool { return r == ',' } - subsysCPU := []byte("cpu") - - for _, subsys := range bytes.FieldsFunc(col, splitCSV) { - if bytes.Equal(subsysCPU, subsys) { - return true - } - } - return false -} - -func isHex(r rune) bool { - return ('0' <= r && r <= '9') || ('a' <= r && r <= 'f') -} - -func validateDockerID(id string) error { - if len(id) != 64 { - return fmt.Errorf("%s is not %d characters long", id, dockerIDLength) - } - - for _, c := range id { - if !isHex(c) { - return fmt.Errorf("Character: %c is not hex in string %s", c, id) - } - } - - return nil -} diff --git a/internal/sysinfo/docker_test.go b/internal/sysinfo/docker_test.go deleted file mode 100644 index 881aeea76..000000000 --- a/internal/sysinfo/docker_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "bytes" - "path/filepath" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestDockerIDCrossAgent(t *testing.T) { - var testCases []struct { - File string `json:"filename"` - ID string `json:"containerId"` - } - - dir := "docker_container_id" - err := crossagent.ReadJSON(filepath.Join(dir, "cases.json"), &testCases) - if err != nil { - t.Fatal(err) - } - - for _, test := range testCases { - file := filepath.Join(dir, test.File) - input, err := crossagent.ReadFile(file) - if err != nil { - t.Error(err) - continue - } - - got, _ := parseDockerID(bytes.NewReader(input)) - if got != test.ID { - t.Errorf("%s != %s", got, test.ID) - } - } -} - -func TestDockerIDValidation(t *testing.T) { - err := validateDockerID("baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1239") - if nil != err { - t.Error("Validation should pass with a 64-character hex string.") - } - err = validateDockerID("39ffbba") - if nil == err { - t.Error("Validation should have failed with short string.") - } - err = validateDockerID("z000000000000000000000000000000000000000000000000100000000000000") - if nil == err { - t.Error("Validation should have failed with non-hex characters.") - } -} diff --git a/internal/sysinfo/errors.go b/internal/sysinfo/errors.go deleted file mode 100644 index 1aa784b47..000000000 --- a/internal/sysinfo/errors.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "errors" -) - -var ( - // ErrFeatureUnsupported indicates unsupported platform. - ErrFeatureUnsupported = errors.New("That feature is not supported on this platform") -) diff --git a/internal/sysinfo/hostname_generic.go b/internal/sysinfo/hostname_generic.go deleted file mode 100644 index a33760554..000000000 --- a/internal/sysinfo/hostname_generic.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build !linux - -package sysinfo - -import "os" - -// Hostname returns the host name. -func Hostname() (string, error) { - return os.Hostname() -} diff --git a/internal/sysinfo/hostname_linux.go b/internal/sysinfo/hostname_linux.go deleted file mode 100644 index a826f200c..000000000 --- a/internal/sysinfo/hostname_linux.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "os" - "syscall" -) - -// Hostname returns the host name. -func Hostname() (string, error) { - // Try the builtin API first, which is designed to match the output of - // /bin/hostname, and fallback to uname(2) if that fails to match the - // behavior of gethostname(2) as implemented by glibc. On Linux, all - // these method should result in the same value because sethostname(2) - // limits the hostname to 64 bytes, the same size of the nodename field - // returned by uname(2). Note that is correspondence is not true on - // other platforms. - // - // os.Hostname failures should be exceedingly rare, however some systems - // configure SELinux to deny read access to /proc/sys/kernel/hostname. - // Redhat's OpenShift platform for example. os.Hostname can also fail if - // some or all of /proc has been hidden via chroot(2) or manipulation of - // the current processes' filesystem namespace via the cgroups APIs. - // Docker is an example of a tool that can configure such an - // environment. - name, err := os.Hostname() - if err == nil { - return name, nil - } - - var uts syscall.Utsname - if err2 := syscall.Uname(&uts); err2 != nil { - // The man page documents only one possible error for uname(2), - // suggesting that as long as the buffer given is valid, the - // call will never fail. Return the original error in the hope - // it provides more relevant information about why the hostname - // can't be retrieved. - return "", err - } - - // Convert Nodename to a Go string. - buf := make([]byte, 0, len(uts.Nodename)) - for _, c := range uts.Nodename { - if c == 0 { - break - } - buf = append(buf, byte(c)) - } - - return string(buf), nil -} diff --git a/internal/sysinfo/memtotal.go b/internal/sysinfo/memtotal.go deleted file mode 100644 index 31cc0919d..000000000 --- a/internal/sysinfo/memtotal.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "bufio" - "errors" - "io" - "regexp" - "strconv" -) - -// BytesToMebibytes converts bytes into mebibytes. -func BytesToMebibytes(bts uint64) uint64 { - return bts / ((uint64)(1024 * 1024)) -} - -var ( - meminfoRe = regexp.MustCompile(`^MemTotal:\s+([0-9]+)\s+[kK]B$`) - errMemTotalNotFound = errors.New("supported MemTotal not found in /proc/meminfo") -) - -// parseProcMeminfo is used to parse Linux's "/proc/meminfo". It is located -// here so that the relevant cross agent tests will be run on all platforms. -func parseProcMeminfo(f io.Reader) (uint64, error) { - scanner := bufio.NewScanner(f) - for scanner.Scan() { - if m := meminfoRe.FindSubmatch(scanner.Bytes()); m != nil { - kb, err := strconv.ParseUint(string(m[1]), 10, 64) - if err != nil { - return 0, err - } - return kb * 1024, nil - } - } - - err := scanner.Err() - if err == nil { - err = errMemTotalNotFound - } - return 0, err -} diff --git a/internal/sysinfo/memtotal_darwin.go b/internal/sysinfo/memtotal_darwin.go deleted file mode 100644 index 008befd7f..000000000 --- a/internal/sysinfo/memtotal_darwin.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "syscall" - "unsafe" -) - -// PhysicalMemoryBytes returns the total amount of host memory. -func PhysicalMemoryBytes() (uint64, error) { - mib := []int32{6 /* CTL_HW */, 24 /* HW_MEMSIZE */} - - buf := make([]byte, 8) - bufLen := uintptr(8) - - _, _, e1 := syscall.Syscall6(syscall.SYS___SYSCTL, - uintptr(unsafe.Pointer(&mib[0])), uintptr(len(mib)), - uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&bufLen)), - uintptr(0), uintptr(0)) - - if e1 != 0 { - return 0, e1 - } - - if bufLen != 8 { - return 0, syscall.EIO - } - - return *(*uint64)(unsafe.Pointer(&buf[0])), nil -} diff --git a/internal/sysinfo/memtotal_darwin_test.go b/internal/sysinfo/memtotal_darwin_test.go deleted file mode 100644 index d90852f28..000000000 --- a/internal/sysinfo/memtotal_darwin_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "errors" - "os/exec" - "regexp" - "strconv" - "testing" -) - -var re = regexp.MustCompile(`hw\.memsize:\s*(\d+)`) - -func darwinSysctlMemoryBytes() (uint64, error) { - out, err := exec.Command("/usr/sbin/sysctl", "hw.memsize").Output() - if err != nil { - return 0, err - } - - match := re.FindSubmatch(out) - if match == nil { - return 0, errors.New("memory size not found in sysctl output") - } - - bts, err := strconv.ParseUint(string(match[1]), 10, 64) - if err != nil { - return 0, err - } - - return bts, nil -} - -func TestPhysicalMemoryBytes(t *testing.T) { - mem, err := PhysicalMemoryBytes() - if err != nil { - t.Fatal(err) - } - - mem2, err := darwinSysctlMemoryBytes() - if nil != err { - t.Fatal(err) - } - - if mem != mem2 { - t.Error(mem, mem2) - } -} diff --git a/internal/sysinfo/memtotal_freebsd.go b/internal/sysinfo/memtotal_freebsd.go deleted file mode 100644 index f97337099..000000000 --- a/internal/sysinfo/memtotal_freebsd.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "syscall" - "unsafe" -) - -// PhysicalMemoryBytes returns the total amount of host memory. -func PhysicalMemoryBytes() (uint64, error) { - mib := []int32{6 /* CTL_HW */, 5 /* HW_PHYSMEM */} - - buf := make([]byte, 8) - bufLen := uintptr(8) - - _, _, e1 := syscall.Syscall6(syscall.SYS___SYSCTL, - uintptr(unsafe.Pointer(&mib[0])), uintptr(len(mib)), - uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&bufLen)), - uintptr(0), uintptr(0)) - - if e1 != 0 { - return 0, e1 - } - - switch bufLen { - case 4: - return uint64(*(*uint32)(unsafe.Pointer(&buf[0]))), nil - case 8: - return *(*uint64)(unsafe.Pointer(&buf[0])), nil - default: - return 0, syscall.EIO - } -} diff --git a/internal/sysinfo/memtotal_freebsd_test.go b/internal/sysinfo/memtotal_freebsd_test.go deleted file mode 100644 index 82a3f1fda..000000000 --- a/internal/sysinfo/memtotal_freebsd_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "errors" - "os/exec" - "regexp" - "strconv" - "testing" -) - -var re = regexp.MustCompile(`hw\.physmem:\s*(\d+)`) - -func freebsdSysctlMemoryBytes() (uint64, error) { - out, err := exec.Command("/sbin/sysctl", "hw.physmem").Output() - if err != nil { - return 0, err - } - - match := re.FindSubmatch(out) - if match == nil { - return 0, errors.New("memory size not found in sysctl output") - } - - bts, err := strconv.ParseUint(string(match[1]), 10, 64) - if err != nil { - return 0, err - } - - return bts, nil -} - -func TestPhysicalMemoryBytes(t *testing.T) { - mem, err := PhysicalMemoryBytes() - if err != nil { - t.Fatal(err) - } - - mem2, err := freebsdSysctlMemoryBytes() - if nil != err { - t.Fatal(err) - } - - if mem != mem2 { - t.Error(mem, mem2) - } -} diff --git a/internal/sysinfo/memtotal_linux.go b/internal/sysinfo/memtotal_linux.go deleted file mode 100644 index d5b0b8c72..000000000 --- a/internal/sysinfo/memtotal_linux.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import "os" - -// PhysicalMemoryBytes returns the total amount of host memory. -func PhysicalMemoryBytes() (uint64, error) { - f, err := os.Open("/proc/meminfo") - if err != nil { - return 0, err - } - defer f.Close() - - return parseProcMeminfo(f) -} diff --git a/internal/sysinfo/memtotal_openbsd_amd64.go b/internal/sysinfo/memtotal_openbsd_amd64.go deleted file mode 100644 index 2dc20960d..000000000 --- a/internal/sysinfo/memtotal_openbsd_amd64.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "syscall" - "unsafe" -) - -// PhysicalMemoryBytes returns the total amount of host memory. -func PhysicalMemoryBytes() (uint64, error) { - mib := []int32{6 /* CTL_HW */, 19 /* HW_PHYSMEM64 */} - - buf := make([]byte, 8) - bufLen := uintptr(8) - - _, _, e1 := syscall.Syscall6(syscall.SYS___SYSCTL, - uintptr(unsafe.Pointer(&mib[0])), uintptr(len(mib)), - uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&bufLen)), - uintptr(0), uintptr(0)) - - if e1 != 0 { - return 0, e1 - } - - switch bufLen { - case 4: - return uint64(*(*uint32)(unsafe.Pointer(&buf[0]))), nil - case 8: - return *(*uint64)(unsafe.Pointer(&buf[0])), nil - default: - return 0, syscall.EIO - } -} diff --git a/internal/sysinfo/memtotal_openbsd_test.go b/internal/sysinfo/memtotal_openbsd_test.go deleted file mode 100644 index e12140848..000000000 --- a/internal/sysinfo/memtotal_openbsd_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "errors" - "os/exec" - "regexp" - "strconv" - "testing" -) - -var re = regexp.MustCompile(`hw\.physmem=(\d+)`) - -func openbsdSysctlMemoryBytes() (uint64, error) { - out, err := exec.Command("/sbin/sysctl", "hw.physmem").Output() - if err != nil { - return 0, err - } - - match := re.FindSubmatch(out) - if match == nil { - return 0, errors.New("memory size not found in sysctl output") - } - - bts, err := strconv.ParseUint(string(match[1]), 10, 64) - if err != nil { - return 0, err - } - - return bts, nil -} - -func TestPhysicalMemoryBytes(t *testing.T) { - mem, err := PhysicalMemoryBytes() - if err != nil { - t.Fatal(err) - } - - mem2, err := openbsdSysctlMemoryBytes() - if nil != err { - t.Fatal(err) - } - - if mem != mem2 { - t.Errorf("Expected %d, got %d\n", mem2, mem) - } -} diff --git a/internal/sysinfo/memtotal_solaris.go b/internal/sysinfo/memtotal_solaris.go deleted file mode 100644 index fe2906903..000000000 --- a/internal/sysinfo/memtotal_solaris.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -/* -#include -*/ -import "C" - -// PhysicalMemoryBytes returns the total amount of host memory. -func PhysicalMemoryBytes() (uint64, error) { - // The function we're calling on Solaris is - // long sysconf(int name); - var pages C.long - var pagesizeBytes C.long - var err error - - pagesizeBytes, err = C.sysconf(C._SC_PAGE_SIZE) - if pagesizeBytes < 1 { - return 0, err - } - pages, err = C.sysconf(C._SC_PHYS_PAGES) - if pages < 1 { - return 0, err - } - - return uint64(pages) * uint64(pagesizeBytes), nil -} diff --git a/internal/sysinfo/memtotal_solaris_test.go b/internal/sysinfo/memtotal_solaris_test.go deleted file mode 100644 index 739a9774f..000000000 --- a/internal/sysinfo/memtotal_solaris_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "errors" - "os/exec" - "regexp" - "strconv" - "strings" - "testing" -) - -func TestPhysicalMemoryBytes(t *testing.T) { - prtconf, err := prtconfMemoryBytes() - if err != nil { - t.Fatal(err) - } - - sysconf, err := PhysicalMemoryBytes() - if err != nil { - t.Fatal(err) - } - - // The pagesize*pages calculation, although standard (the JVM, at least, - // uses this approach), doesn't match up exactly with the number - // returned by prtconf. - if sysconf > prtconf || sysconf < (prtconf-prtconf/20) { - t.Fatal(prtconf, sysconf) - } -} - -var ( - ptrconfRe = regexp.MustCompile(`[Mm]emory\s*size:\s*([0-9]+)\s*([a-zA-Z]+)`) -) - -func prtconfMemoryBytes() (uint64, error) { - output, err := exec.Command("/usr/sbin/prtconf").Output() - if err != nil { - return 0, err - } - - m := ptrconfRe.FindSubmatch(output) - if m == nil { - return 0, errors.New("memory size not found in prtconf output") - } - - size, err := strconv.ParseUint(string(m[1]), 10, 64) - if err != nil { - return 0, err - } - - switch strings.ToLower(string(m[2])) { - case "megabytes", "mb": - return size * 1024 * 1024, nil - case "kilobytes", "kb": - return size * 1024, nil - default: - return 0, errors.New("couldn't parse memory size in prtconf output") - } -} diff --git a/internal/sysinfo/memtotal_test.go b/internal/sysinfo/memtotal_test.go deleted file mode 100644 index 266d45b57..000000000 --- a/internal/sysinfo/memtotal_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "os" - "regexp" - "strconv" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestMemTotal(t *testing.T) { - var fileRe = regexp.MustCompile(`meminfo_([0-9]+)MB.txt$`) - var ignoreFile = regexp.MustCompile(`README\.md$`) - - testCases, err := crossagent.ReadDir("proc_meminfo") - if err != nil { - t.Fatal(err) - } - - for _, testFile := range testCases { - if ignoreFile.MatchString(testFile) { - continue - } - - matches := fileRe.FindStringSubmatch(testFile) - - if matches == nil || len(matches) < 2 { - t.Error(testFile, matches) - continue - } - - expect, err := strconv.ParseUint(matches[1], 10, 64) - if err != nil { - t.Error(err) - continue - } - - input, err := os.Open(testFile) - if err != nil { - t.Error(err) - continue - } - bts, err := parseProcMeminfo(input) - input.Close() - mib := BytesToMebibytes(bts) - if err != nil { - t.Error(err) - } else if mib != expect { - t.Error(bts, expect) - } - } -} diff --git a/internal/sysinfo/memtotal_windows.go b/internal/sysinfo/memtotal_windows.go deleted file mode 100644 index 984e033c5..000000000 --- a/internal/sysinfo/memtotal_windows.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "syscall" - "unsafe" -) - -// PhysicalMemoryBytes returns the total amount of host memory. -func PhysicalMemoryBytes() (uint64, error) { - // https://msdn.microsoft.com/en-us/library/windows/desktop/cc300158(v=vs.85).aspx - // http://stackoverflow.com/questions/30743070/query-total-physical-memory-in-windows-with-golang - mod := syscall.NewLazyDLL("kernel32.dll") - proc := mod.NewProc("GetPhysicallyInstalledSystemMemory") - var memkb uint64 - - ret, _, err := proc.Call(uintptr(unsafe.Pointer(&memkb))) - // return value TRUE(1) succeeds, FAILED(0) fails - if ret != 1 { - return 0, err - } - - return memkb * 1024, nil -} diff --git a/internal/sysinfo/usage.go b/internal/sysinfo/usage.go deleted file mode 100644 index b744f65b5..000000000 --- a/internal/sysinfo/usage.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "time" -) - -// Usage contains process process times. -type Usage struct { - System time.Duration - User time.Duration -} diff --git a/internal/sysinfo/usage_posix.go b/internal/sysinfo/usage_posix.go deleted file mode 100644 index 4d758dea1..000000000 --- a/internal/sysinfo/usage_posix.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build !windows - -package sysinfo - -import ( - "syscall" - "time" -) - -func timevalToDuration(tv syscall.Timeval) time.Duration { - return time.Duration(tv.Nano()) * time.Nanosecond -} - -// GetUsage gathers process times. -func GetUsage() (Usage, error) { - ru := syscall.Rusage{} - err := syscall.Getrusage(syscall.RUSAGE_SELF, &ru) - if err != nil { - return Usage{}, err - } - - return Usage{ - System: timevalToDuration(ru.Stime), - User: timevalToDuration(ru.Utime), - }, nil -} diff --git a/internal/sysinfo/usage_windows.go b/internal/sysinfo/usage_windows.go deleted file mode 100644 index 6012a0f80..000000000 --- a/internal/sysinfo/usage_windows.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package sysinfo - -import ( - "syscall" - "time" -) - -func filetimeToDuration(ft *syscall.Filetime) time.Duration { - ns := ft.Nanoseconds() - return time.Duration(ns) -} - -// GetUsage gathers process times. -func GetUsage() (Usage, error) { - var creationTime syscall.Filetime - var exitTime syscall.Filetime - var kernelTime syscall.Filetime - var userTime syscall.Filetime - - handle, err := syscall.GetCurrentProcess() - if err != nil { - return Usage{}, err - } - - err = syscall.GetProcessTimes(handle, &creationTime, &exitTime, &kernelTime, &userTime) - if err != nil { - return Usage{}, err - } - - return Usage{ - System: filetimeToDuration(&kernelTime), - User: filetimeToDuration(&userTime), - }, nil -} diff --git a/internal/tools/interface-wrapping/driver_conn.json b/internal/tools/interface-wrapping/driver_conn.json deleted file mode 100644 index f2076332e..000000000 --- a/internal/tools/interface-wrapping/driver_conn.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "comment": "used in wrapping driver.Conn", - "variable_name": "conn", - "test_variable_name": "conn.original", - "required_interfaces": [ - "driver.Conn" - ], - "optional_interfaces": [ - "driver.ConnBeginTx", - "driver.ConnPrepareContext", - "driver.Execer", - "driver.ExecerContext", - "driver.NamedValueChecker", - "driver.Pinger", - "driver.Queryer", - "driver.QueryerContext" - ] -} diff --git a/internal/tools/interface-wrapping/driver_driver.json b/internal/tools/interface-wrapping/driver_driver.json deleted file mode 100644 index 81b57e283..000000000 --- a/internal/tools/interface-wrapping/driver_driver.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "comment": "used in wrapping driver.Driver", - "variable_name": "dv", - "test_variable_name": "dv.original", - "required_interfaces": [ - "driver.Driver" - ], - "optional_interfaces": [ - "driver.DriverContext" - ] -} diff --git a/internal/tools/interface-wrapping/driver_stmt.json b/internal/tools/interface-wrapping/driver_stmt.json deleted file mode 100644 index 2c569c238..000000000 --- a/internal/tools/interface-wrapping/driver_stmt.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "comment": "used in wrapping driver.Stmt", - "variable_name": "stmt", - "test_variable_name": "stmt.original", - "required_interfaces": [ - "driver.Stmt" - ], - "optional_interfaces": [ - "driver.ColumnConverter", - "driver.NamedValueChecker", - "driver.StmtExecContext", - "driver.StmtQueryContext" - ] -} diff --git a/internal/tools/interface-wrapping/main.go b/internal/tools/interface-wrapping/main.go deleted file mode 100644 index 607f85ca3..000000000 --- a/internal/tools/interface-wrapping/main.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" -) - -// This program is generates code for wrapping interfaces which implement -// optional interfaces. For some context on the problem this solves, read: -// https://blog.merovius.de/2017/07/30/the-trouble-with-optional-interfaces.html - -// This problem takes one of the json files in this directory as input: -// eg. go run main.go transaction_response_writer.json - -func main() { - if len(os.Args) < 2 { - fmt.Println("provide input file") - os.Exit(1) - } - filename := os.Args[1] - inputBytes, err := ioutil.ReadFile(filename) - if nil != err { - fmt.Println(fmt.Errorf("unable to read %v: %v", filename, err)) - os.Exit(1) - } - - var input struct { - // variableName must implement all of the required interfaces - // and all of the optional interfaces. It will be used to - // populate the fields of anonymous structs which have - // interfaces embedded. - VariableName string `json:"variable_name"` - // variableName is the variable that will be tested against the - // optional interfaces. It is the "thing being wrapped" whose - // behavior we seek to emulate. - TestVariableName string `json:"test_variable_name"` - RequiresInterfaces []string `json:"required_interfaces"` - OptionalInterfaces []string `json:"optional_interfaces"` - } - - err = json.Unmarshal(inputBytes, &input) - if nil != err { - fmt.Println(fmt.Errorf("unable to unmarshal input: %v", err)) - os.Exit(1) - } - - bitflagVariables := make([]string, len(input.OptionalInterfaces)) - for idx := range input.OptionalInterfaces { - bitflagVariables[idx] = fmt.Sprintf("i%d", idx) - } - - fmt.Println("// GENERATED CODE DO NOT MODIFY") - fmt.Println("// This code generated by internal/tools/interface-wrapping") - fmt.Println("var (") - for idx := range input.OptionalInterfaces { - fmt.Println(fmt.Sprintf("%s int32 = 1 << %d", bitflagVariables[idx], idx)) - } - fmt.Println(")") - // interfaceSet is a bitset whose value represents the optional - // interfaces that $input.TestVariableName implements. - fmt.Println("var interfaceSet int32") - for idx, inter := range input.OptionalInterfaces { - fmt.Println(fmt.Sprintf("if _, ok := %s.(%s); ok {", input.TestVariableName, inter)) - fmt.Println(fmt.Sprintf("interfaceSet |= %s", bitflagVariables[idx])) - fmt.Println("}") - } - permutations := make([][]int, 1< 0 { - cs += " | " - } - cs += bitflagVariables[elem] - } - if cs == "" { - fmt.Println("default: // No optional interfaces implemented") - } else { - fmt.Println(fmt.Sprintf("case %s:", cs)) - } - fmt.Println("return struct {") - for _, required := range input.RequiresInterfaces { - fmt.Println(required) - } - for _, elem := range permutation { - fmt.Println(input.OptionalInterfaces[elem]) - } - totalImplements := len(input.RequiresInterfaces) + len(permutation) - var varList string - for i := 0; i < totalImplements; i++ { - if i > 0 { - varList += ", " - } - varList += input.VariableName - } - fmt.Println("} { " + varList + " }") - } - fmt.Println("}") -} diff --git a/internal/tools/interface-wrapping/transaction_response_writer.json b/internal/tools/interface-wrapping/transaction_response_writer.json deleted file mode 100644 index a0d8f5be5..000000000 --- a/internal/tools/interface-wrapping/transaction_response_writer.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "comment": "used in internal_response_writer.go", - "variable_name": "thd", - "test_variable_name": "thd.txn.writer", - "required_interfaces": [ - "threadWithExtras" - ], - "optional_interfaces": [ - "http.CloseNotifier", - "http.Flusher", - "http.Hijacker", - "io.ReaderFrom" - ] -} diff --git a/internal/tools/rules/main.go b/internal/tools/rules/main.go deleted file mode 100644 index 0ee601258..000000000 --- a/internal/tools/rules/main.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - - "github.com/newrelic/go-agent/internal" -) - -func fail(reason string) { - fmt.Println(reason) - os.Exit(1) -} - -func main() { - if len(os.Args) < 3 { - fail("improper usage: ./rules path/to/reply_file input") - } - - connectReplyFile := os.Args[1] - name := os.Args[2] - - data, err := ioutil.ReadFile(connectReplyFile) - if nil != err { - fail(fmt.Sprintf("unable to open '%s': %s", connectReplyFile, err)) - } - - var reply internal.ConnectReply - err = json.Unmarshal(data, &reply) - if nil != err { - fail(fmt.Sprintf("unable unmarshal reply: %s", err)) - } - - // Metric Rules - out := reply.MetricRules.Apply(name) - fmt.Println("metric rules applied:", out) - - // Url Rules + Txn Name Rules + Segment Term Rules - - out = internal.CreateFullTxnName(name, &reply, true) - fmt.Println("treated as web txn name:", out) - - out = internal.CreateFullTxnName(name, &reply, false) - fmt.Println("treated as backround txn name:", out) -} diff --git a/internal/tools/uncompress-serverless/main.go b/internal/tools/uncompress-serverless/main.go deleted file mode 100644 index a5d3f039a..000000000 --- a/internal/tools/uncompress-serverless/main.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "encoding/json" - "fmt" - "github.com/newrelic/go-agent/internal" - "os" -) - -// This tool will take an encoded, compressed serverless payload and print it out in a human readable format. -// To use it on a Mac with Bash, copy the payload to the clipboard (the whole thing - `[2,"NR_LAMBDA_MONITORING",{"metadata_version"...]` -// without backticks) and then run the app using pbpaste. Example from the root of the project directory: -// -// go run internal/tools/uncompress-serverless/main.go $(pbpaste) -func main() { - - compressed := []byte(os.Args[1]) - metadata, uncompressedData, e := internal.ParseServerlessPayload(compressed) - if nil != e { - panic(e) - } - js, _ := json.MarshalIndent(map[string]interface{}{"metadata": metadata, "data": uncompressedData}, "", " ") - fmt.Println(string(js)) - -} diff --git a/internal/trace_id_generator.go b/internal/trace_id_generator.go deleted file mode 100644 index ce5ca8028..000000000 --- a/internal/trace_id_generator.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "fmt" - "math/rand" - "sync" -) - -// TraceIDGenerator creates identifiers for distributed tracing. -type TraceIDGenerator struct { - sync.Mutex - rnd *rand.Rand -} - -// NewTraceIDGenerator creates a new trace identifier generator. -func NewTraceIDGenerator(seed int64) *TraceIDGenerator { - return &TraceIDGenerator{ - rnd: rand.New(rand.NewSource(seed)), - } -} - -// GenerateTraceID creates a new trace identifier. -func (tg *TraceIDGenerator) GenerateTraceID() string { - tg.Lock() - defer tg.Unlock() - - u1 := tg.rnd.Uint32() - u2 := tg.rnd.Uint32() - bits := (uint64(u1) << 32) | uint64(u2) - return fmt.Sprintf("%016x", bits) -} diff --git a/internal/trace_id_generator_test.go b/internal/trace_id_generator_test.go deleted file mode 100644 index 98e018aed..000000000 --- a/internal/trace_id_generator_test.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import "testing" - -func TestTraceIDGenerator(t *testing.T) { - tg := NewTraceIDGenerator(12345) - id := tg.GenerateTraceID() - if id != "d9466896a525ccbf" { - t.Error(id) - } -} diff --git a/internal/tracing.go b/internal/tracing.go deleted file mode 100644 index 8e4375c3d..000000000 --- a/internal/tracing.go +++ /dev/null @@ -1,771 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "errors" - "fmt" - "net/http" - "net/url" - "time" - - "github.com/newrelic/go-agent/internal/cat" - "github.com/newrelic/go-agent/internal/jsonx" - "github.com/newrelic/go-agent/internal/logger" - "github.com/newrelic/go-agent/internal/sysinfo" -) - -// MarshalJSON limits the number of decimals. -func (p *Priority) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(priorityFormat, *p)), nil -} - -// WriteJSON limits the number of decimals. -func (p Priority) WriteJSON(buf *bytes.Buffer) { - fmt.Fprintf(buf, priorityFormat, p) -} - -// TxnEvent represents a transaction. -// https://source.datanerd.us/agents/agent-specs/blob/master/Transaction-Events-PORTED.md -// https://newrelic.atlassian.net/wiki/display/eng/Agent+Support+for+Synthetics%3A+Forced+Transaction+Traces+and+Analytic+Events -type TxnEvent struct { - FinalName string - Start time.Time - Duration time.Duration - TotalTime time.Duration - Queuing time.Duration - Zone ApdexZone - Attrs *Attributes - DatastoreExternalTotals - CrossProcess TxnCrossProcess - BetterCAT BetterCAT - HasError bool -} - -// BetterCAT stores the transaction's priority and all fields related -// to a DistributedTracer's Cross-Application Trace. -type BetterCAT struct { - Enabled bool - Priority Priority - Sampled bool - Inbound *Payload - ID string -} - -// TraceID returns the trace id. -func (e BetterCAT) TraceID() string { - if nil != e.Inbound { - return e.Inbound.TracedID - } - return e.ID -} - -// TxnData contains the recorded data of a transaction. -type TxnData struct { - TxnEvent - IsWeb bool - Name string // Work in progress name. - Errors TxnErrors // Lazily initialized. - Stop time.Time - ApdexThreshold time.Duration - - stamp segmentStamp - threadIDCounter uint64 - - TraceIDGenerator *TraceIDGenerator - LazilyCalculateSampled func() bool - SpanEventsEnabled bool - rootSpanID string - spanEvents []*SpanEvent - - customSegments map[string]*metricData - datastoreSegments map[DatastoreMetricKey]*metricData - externalSegments map[externalMetricKey]*metricData - messageSegments map[MessageMetricKey]*metricData - - TxnTrace - - SlowQueriesEnabled bool - SlowQueryThreshold time.Duration - SlowQueries *slowQueries - - // These better CAT supportability fields are left outside of - // TxnEvent.BetterCAT to minimize the size of transaction event memory. - DistributedTracingSupport -} - -func (t *TxnData) saveTraceSegment(end segmentEnd, name string, attrs spanAttributeMap, externalGUID string) { - attrs = t.Attrs.filterSpanAttributes(attrs, destSegment) - t.TxnTrace.witnessNode(end, name, attrs, externalGUID) -} - -// Thread contains a segment stack that is used to track segment parenting time -// within a single goroutine. -type Thread struct { - threadID uint64 - stack []segmentFrame - // start and end are used to track the TotalTime this Thread was active. - start time.Time - end time.Time -} - -// RecordActivity indicates that activity happened at this time on this -// goroutine which helps track total time. -func (thread *Thread) RecordActivity(now time.Time) { - if thread.start.IsZero() || now.Before(thread.start) { - thread.start = now - } - if now.After(thread.end) { - thread.end = now - } -} - -// TotalTime returns the amount to time that this thread contributes to the -// total time. -func (thread *Thread) TotalTime() time.Duration { - if thread.start.Before(thread.end) { - return thread.end.Sub(thread.start) - } - return 0 -} - -// NewThread returns a new Thread to track segments in a new goroutine. -func NewThread(txndata *TxnData) *Thread { - // Each thread needs a unique ID. - txndata.threadIDCounter++ - return &Thread{ - threadID: txndata.threadIDCounter, - } -} - -type segmentStamp uint64 - -type segmentTime struct { - Stamp segmentStamp - Time time.Time -} - -// SegmentStartTime is embedded into the top level segments (rather than -// segmentTime) to minimize the structure sizes to minimize allocations. -type SegmentStartTime struct { - Stamp segmentStamp - Depth int -} - -type stringJSONWriter string - -func (s stringJSONWriter) WriteJSON(buf *bytes.Buffer) { - jsonx.AppendString(buf, string(s)) -} - -// spanAttributeMap is used for span attributes and segment attributes. The -// value is a jsonWriter to allow for segment query parameters. -type spanAttributeMap map[SpanAttribute]jsonWriter - -func (m *spanAttributeMap) addString(key SpanAttribute, val string) { - if "" != val { - m.add(key, stringJSONWriter(val)) - } -} - -func (m *spanAttributeMap) add(key SpanAttribute, val jsonWriter) { - if *m == nil { - *m = make(spanAttributeMap) - } - (*m)[key] = val -} - -func (m spanAttributeMap) copy() spanAttributeMap { - if len(m) == 0 { - return nil - } - cpy := make(spanAttributeMap, len(m)) - for k, v := range m { - cpy[k] = v - } - return cpy -} - -type segmentFrame struct { - segmentTime - children time.Duration - spanID string - attributes spanAttributeMap -} - -type segmentEnd struct { - start segmentTime - stop segmentTime - duration time.Duration - exclusive time.Duration - SpanID string - ParentID string - threadID uint64 - attributes spanAttributeMap -} - -func (end segmentEnd) spanEvent() *SpanEvent { - if "" == end.SpanID { - return nil - } - return &SpanEvent{ - GUID: end.SpanID, - ParentID: end.ParentID, - Timestamp: end.start.Time, - Duration: end.duration, - Attributes: end.attributes, - IsEntrypoint: false, - } -} - -const ( - datastoreProductUnknown = "Unknown" - datastoreOperationUnknown = "other" -) - -// HasErrors indicates whether the transaction had errors. -func (t *TxnData) HasErrors() bool { - return len(t.Errors) > 0 -} - -func (t *TxnData) time(now time.Time) segmentTime { - // Update the stamp before using it so that a 0 stamp can be special. - t.stamp++ - return segmentTime{ - Time: now, - Stamp: t.stamp, - } -} - -// AddAgentSpanAttribute allows attributes to be added to spans. -func (thread *Thread) AddAgentSpanAttribute(key SpanAttribute, val string) { - if len(thread.stack) > 0 { - thread.stack[len(thread.stack)-1].attributes.addString(key, val) - } -} - -// StartSegment begins a segment. -func StartSegment(t *TxnData, thread *Thread, now time.Time) SegmentStartTime { - tm := t.time(now) - thread.stack = append(thread.stack, segmentFrame{ - segmentTime: tm, - children: 0, - }) - - return SegmentStartTime{ - Stamp: tm.Stamp, - Depth: len(thread.stack) - 1, - } -} - -func (t *TxnData) getRootSpanID() string { - if "" == t.rootSpanID { - t.rootSpanID = t.TraceIDGenerator.GenerateTraceID() - } - return t.rootSpanID -} - -// CurrentSpanIdentifier returns the identifier of the span at the top of the -// segment stack. -func (t *TxnData) CurrentSpanIdentifier(thread *Thread) string { - if 0 == len(thread.stack) { - return t.getRootSpanID() - } - if "" == thread.stack[len(thread.stack)-1].spanID { - thread.stack[len(thread.stack)-1].spanID = t.TraceIDGenerator.GenerateTraceID() - } - return thread.stack[len(thread.stack)-1].spanID -} - -func (t *TxnData) saveSpanEvent(e *SpanEvent) { - e.Attributes = t.Attrs.filterSpanAttributes(e.Attributes, destSpan) - if len(t.spanEvents) < MaxSpanEvents { - t.spanEvents = append(t.spanEvents, e) - } -} - -var ( - errMalformedSegment = errors.New("segment identifier malformed: perhaps unsafe code has modified it?") - errSegmentOrder = errors.New(`improper segment use: the Transaction must be used ` + - `in a single goroutine and segments must be ended in "last started first ended" order: ` + - `see https://github.com/newrelic/go-agent/blob/master/GUIDE.md#segments`) -) - -func endSegment(t *TxnData, thread *Thread, start SegmentStartTime, now time.Time) (segmentEnd, error) { - if 0 == start.Stamp { - return segmentEnd{}, errMalformedSegment - } - if start.Depth >= len(thread.stack) { - return segmentEnd{}, errSegmentOrder - } - if start.Depth < 0 { - return segmentEnd{}, errMalformedSegment - } - frame := thread.stack[start.Depth] - if start.Stamp != frame.Stamp { - return segmentEnd{}, errSegmentOrder - } - - var children time.Duration - for i := start.Depth; i < len(thread.stack); i++ { - children += thread.stack[i].children - } - s := segmentEnd{ - stop: t.time(now), - start: frame.segmentTime, - attributes: frame.attributes, - } - if s.stop.Time.After(s.start.Time) { - s.duration = s.stop.Time.Sub(s.start.Time) - } - if s.duration > children { - s.exclusive = s.duration - children - } - - // Note that we expect (depth == (len(t.stack) - 1)). However, if - // (depth < (len(t.stack) - 1)), that's ok: could be a panic popped - // some stack frames (and the consumer was not using defer). - - if start.Depth > 0 { - thread.stack[start.Depth-1].children += s.duration - } - - thread.stack = thread.stack[0:start.Depth] - - if t.SpanEventsEnabled && t.LazilyCalculateSampled() { - s.SpanID = frame.spanID - if "" == s.SpanID { - s.SpanID = t.TraceIDGenerator.GenerateTraceID() - } - // Note that the current span identifier is the parent's - // identifier because we've already popped the segment that's - // ending off of the stack. - s.ParentID = t.CurrentSpanIdentifier(thread) - } - - s.threadID = thread.threadID - - thread.RecordActivity(s.start.Time) - thread.RecordActivity(s.stop.Time) - - return s, nil -} - -// EndBasicSegment ends a basic segment. -func EndBasicSegment(t *TxnData, thread *Thread, start SegmentStartTime, now time.Time, name string) error { - end, err := endSegment(t, thread, start, now) - if nil != err { - return err - } - if nil == t.customSegments { - t.customSegments = make(map[string]*metricData) - } - m := metricDataFromDuration(end.duration, end.exclusive) - if data, ok := t.customSegments[name]; ok { - data.aggregate(m) - } else { - // Use `new` in place of &m so that m is not - // automatically moved to the heap. - cpy := new(metricData) - *cpy = m - t.customSegments[name] = cpy - } - - if t.TxnTrace.considerNode(end) { - attributes := end.attributes.copy() - t.saveTraceSegment(end, customSegmentMetric(name), attributes, "") - } - - if evt := end.spanEvent(); evt != nil { - evt.Name = customSegmentMetric(name) - evt.Category = spanCategoryGeneric - t.saveSpanEvent(evt) - } - - return nil -} - -// EndExternalParams contains the parameters for EndExternalSegment. -type EndExternalParams struct { - TxnData *TxnData - Thread *Thread - Start SegmentStartTime - Now time.Time - Logger logger.Logger - Response *http.Response - URL *url.URL - Host string - Library string - Method string -} - -// EndExternalSegment ends an external segment. -func EndExternalSegment(p EndExternalParams) error { - t := p.TxnData - end, err := endSegment(t, p.Thread, p.Start, p.Now) - if nil != err { - return err - } - - // Use the Host field if present, otherwise use host in the URL. - if p.Host == "" && p.URL != nil { - p.Host = p.URL.Host - } - if p.Host == "" { - p.Host = "unknown" - } - if p.Library == "" { - p.Library = "http" - } - - var appData *cat.AppDataHeader - if p.Response != nil { - hdr := HTTPHeaderToAppData(p.Response.Header) - appData, err = t.CrossProcess.ParseAppData(hdr) - if err != nil { - if p.Logger.DebugEnabled() { - p.Logger.Debug("failure to parse cross application response header", map[string]interface{}{ - "err": err.Error(), - "header": hdr, - }) - } - } - } - - var crossProcessID string - var transactionName string - var transactionGUID string - if appData != nil { - crossProcessID = appData.CrossProcessID - transactionName = appData.TransactionName - transactionGUID = appData.TransactionGUID - } - - key := externalMetricKey{ - Host: p.Host, - Library: p.Library, - Method: p.Method, - ExternalCrossProcessID: crossProcessID, - ExternalTransactionName: transactionName, - } - if nil == t.externalSegments { - t.externalSegments = make(map[externalMetricKey]*metricData) - } - t.externalCallCount++ - t.externalDuration += end.duration - m := metricDataFromDuration(end.duration, end.exclusive) - if data, ok := t.externalSegments[key]; ok { - data.aggregate(m) - } else { - // Use `new` in place of &m so that m is not - // automatically moved to the heap. - cpy := new(metricData) - *cpy = m - t.externalSegments[key] = cpy - } - - if t.TxnTrace.considerNode(end) { - attributes := end.attributes.copy() - if p.Library == "http" { - attributes.addString(spanAttributeHTTPURL, SafeURL(p.URL)) - } - t.saveTraceSegment(end, key.scopedMetric(), attributes, transactionGUID) - } - - if evt := end.spanEvent(); evt != nil { - evt.Name = key.scopedMetric() - evt.Category = spanCategoryHTTP - evt.Kind = "client" - evt.Component = p.Library - if p.Library == "http" { - evt.Attributes.addString(spanAttributeHTTPURL, SafeURL(p.URL)) - evt.Attributes.addString(spanAttributeHTTPMethod, p.Method) - } - t.saveSpanEvent(evt) - } - - return nil -} - -// EndMessageParams contains the parameters for EndMessageSegment. -type EndMessageParams struct { - TxnData *TxnData - Thread *Thread - Start SegmentStartTime - Now time.Time - Logger logger.Logger - DestinationName string - Library string - DestinationType string - DestinationTemp bool -} - -// EndMessageSegment ends an external segment. -func EndMessageSegment(p EndMessageParams) error { - t := p.TxnData - end, err := endSegment(t, p.Thread, p.Start, p.Now) - if nil != err { - return err - } - - key := MessageMetricKey{ - Library: p.Library, - DestinationType: p.DestinationType, - DestinationName: p.DestinationName, - DestinationTemp: p.DestinationTemp, - } - - if nil == t.messageSegments { - t.messageSegments = make(map[MessageMetricKey]*metricData) - } - m := metricDataFromDuration(end.duration, end.exclusive) - if data, ok := t.messageSegments[key]; ok { - data.aggregate(m) - } else { - // Use `new` in place of &m so that m is not - // automatically moved to the heap. - cpy := new(metricData) - *cpy = m - t.messageSegments[key] = cpy - } - - if t.TxnTrace.considerNode(end) { - attributes := end.attributes.copy() - t.saveTraceSegment(end, key.Name(), attributes, "") - } - - if evt := end.spanEvent(); evt != nil { - evt.Name = key.Name() - evt.Category = spanCategoryGeneric - t.saveSpanEvent(evt) - } - - return nil -} - -// EndDatastoreParams contains the parameters for EndDatastoreSegment. -type EndDatastoreParams struct { - TxnData *TxnData - Thread *Thread - Start SegmentStartTime - Now time.Time - Product string - Collection string - Operation string - ParameterizedQuery string - QueryParameters map[string]interface{} - Host string - PortPathOrID string - Database string -} - -const ( - unknownDatastoreHost = "unknown" - unknownDatastorePortPathOrID = "unknown" -) - -var ( - // ThisHost is the system hostname. - ThisHost = func() string { - if h, err := sysinfo.Hostname(); nil == err { - return h - } - return unknownDatastoreHost - }() - hostsToReplace = map[string]struct{}{ - "localhost": {}, - "127.0.0.1": {}, - "0.0.0.0": {}, - "0:0:0:0:0:0:0:1": {}, - "::1": {}, - "0:0:0:0:0:0:0:0": {}, - "::": {}, - } -) - -func (t TxnData) slowQueryWorthy(d time.Duration) bool { - return t.SlowQueriesEnabled && (d >= t.SlowQueryThreshold) -} - -func datastoreSpanAddress(host, portPathOrID string) string { - if "" != host && "" != portPathOrID { - return host + ":" + portPathOrID - } - if "" != host { - return host - } - return portPathOrID -} - -// EndDatastoreSegment ends a datastore segment. -func EndDatastoreSegment(p EndDatastoreParams) error { - end, err := endSegment(p.TxnData, p.Thread, p.Start, p.Now) - if nil != err { - return err - } - if p.Operation == "" { - p.Operation = datastoreOperationUnknown - } - if p.Product == "" { - p.Product = datastoreProductUnknown - } - if p.Host == "" && p.PortPathOrID != "" { - p.Host = unknownDatastoreHost - } - if p.PortPathOrID == "" && p.Host != "" { - p.PortPathOrID = unknownDatastorePortPathOrID - } - if _, ok := hostsToReplace[p.Host]; ok { - p.Host = ThisHost - } - - // We still want to create a slowQuery if the consumer has not provided - // a Query string (or it has been removed by LASP) since the stack trace - // has value. - if p.ParameterizedQuery == "" { - collection := p.Collection - if "" == collection { - collection = "unknown" - } - p.ParameterizedQuery = fmt.Sprintf(`'%s' on '%s' using '%s'`, - p.Operation, collection, p.Product) - } - - key := DatastoreMetricKey{ - Product: p.Product, - Collection: p.Collection, - Operation: p.Operation, - Host: p.Host, - PortPathOrID: p.PortPathOrID, - } - if nil == p.TxnData.datastoreSegments { - p.TxnData.datastoreSegments = make(map[DatastoreMetricKey]*metricData) - } - p.TxnData.datastoreCallCount++ - p.TxnData.datastoreDuration += end.duration - m := metricDataFromDuration(end.duration, end.exclusive) - if data, ok := p.TxnData.datastoreSegments[key]; ok { - data.aggregate(m) - } else { - // Use `new` in place of &m so that m is not - // automatically moved to the heap. - cpy := new(metricData) - *cpy = m - p.TxnData.datastoreSegments[key] = cpy - } - - scopedMetric := datastoreScopedMetric(key) - // errors in QueryParameters must not stop the recording of the segment - queryParams, err := vetQueryParameters(p.QueryParameters) - - if p.TxnData.TxnTrace.considerNode(end) { - attributes := end.attributes.copy() - attributes.addString(spanAttributeDBStatement, p.ParameterizedQuery) - attributes.addString(spanAttributeDBInstance, p.Database) - attributes.addString(spanAttributePeerAddress, datastoreSpanAddress(p.Host, p.PortPathOrID)) - attributes.addString(spanAttributePeerHostname, p.Host) - if len(queryParams) > 0 { - attributes.add(spanAttributeQueryParameters, queryParams) - } - p.TxnData.saveTraceSegment(end, scopedMetric, attributes, "") - } - - if p.TxnData.slowQueryWorthy(end.duration) { - if nil == p.TxnData.SlowQueries { - p.TxnData.SlowQueries = newSlowQueries(maxTxnSlowQueries) - } - p.TxnData.SlowQueries.observeInstance(slowQueryInstance{ - Duration: end.duration, - DatastoreMetric: scopedMetric, - ParameterizedQuery: p.ParameterizedQuery, - QueryParameters: queryParams, - Host: p.Host, - PortPathOrID: p.PortPathOrID, - DatabaseName: p.Database, - StackTrace: GetStackTrace(), - }) - } - - if evt := end.spanEvent(); evt != nil { - evt.Name = scopedMetric - evt.Category = spanCategoryDatastore - evt.Kind = "client" - evt.Component = p.Product - evt.Attributes.addString(spanAttributeDBStatement, p.ParameterizedQuery) - evt.Attributes.addString(spanAttributeDBInstance, p.Database) - evt.Attributes.addString(spanAttributePeerAddress, datastoreSpanAddress(p.Host, p.PortPathOrID)) - evt.Attributes.addString(spanAttributePeerHostname, p.Host) - evt.Attributes.addString(spanAttributeDBCollection, p.Collection) - p.TxnData.saveSpanEvent(evt) - } - - return err -} - -// MergeBreakdownMetrics creates segment metrics. -func MergeBreakdownMetrics(t *TxnData, metrics *metricTable) { - scope := t.FinalName - isWeb := t.IsWeb - // Custom Segment Metrics - for key, data := range t.customSegments { - name := customSegmentMetric(key) - // Unscoped - metrics.add(name, "", *data, unforced) - // Scoped - metrics.add(name, scope, *data, unforced) - } - - // External Segment Metrics - for key, data := range t.externalSegments { - metrics.add(externalRollupMetric.all, "", *data, forced) - metrics.add(externalRollupMetric.webOrOther(isWeb), "", *data, forced) - - hostMetric := externalHostMetric(key) - metrics.add(hostMetric, "", *data, unforced) - if "" != key.ExternalCrossProcessID && "" != key.ExternalTransactionName { - txnMetric := externalTransactionMetric(key) - - // Unscoped CAT metrics - metrics.add(externalAppMetric(key), "", *data, unforced) - metrics.add(txnMetric, "", *data, unforced) - } - - // Scoped External Metric - metrics.add(key.scopedMetric(), scope, *data, unforced) - } - - // Datastore Segment Metrics - for key, data := range t.datastoreSegments { - metrics.add(datastoreRollupMetric.all, "", *data, forced) - metrics.add(datastoreRollupMetric.webOrOther(isWeb), "", *data, forced) - - product := datastoreProductMetric(key) - metrics.add(product.all, "", *data, forced) - metrics.add(product.webOrOther(isWeb), "", *data, forced) - - if key.Host != "" && key.PortPathOrID != "" { - instance := datastoreInstanceMetric(key) - metrics.add(instance, "", *data, unforced) - } - - operation := datastoreOperationMetric(key) - metrics.add(operation, "", *data, unforced) - - if "" != key.Collection { - statement := datastoreStatementMetric(key) - - metrics.add(statement, "", *data, unforced) - metrics.add(statement, scope, *data, unforced) - } else { - metrics.add(operation, scope, *data, unforced) - } - } - // Message Segment Metrics - for key, data := range t.messageSegments { - metric := key.Name() - metrics.add(metric, scope, *data, unforced) - metrics.add(metric, "", *data, unforced) - } -} diff --git a/internal/tracing_test.go b/internal/tracing_test.go deleted file mode 100644 index 7820a88bd..000000000 --- a/internal/tracing_test.go +++ /dev/null @@ -1,842 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "net/http" - "net/url" - "strconv" - "strings" - "testing" - "time" - - "github.com/newrelic/go-agent/internal/cat" - "github.com/newrelic/go-agent/internal/crossagent" - "github.com/newrelic/go-agent/internal/logger" -) - -func TestStartEndSegment(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - - txndata := &TxnData{} - thread := &Thread{} - token := StartSegment(txndata, thread, start) - stop := start.Add(1 * time.Second) - end, err := endSegment(txndata, thread, token, stop) - if nil != err { - t.Error(err) - } - if end.exclusive != end.duration { - t.Error(end.exclusive, end.duration) - } - if end.duration != 1*time.Second { - t.Error(end.duration) - } - if end.start.Time != start { - t.Error(end.start, start) - } - if end.stop.Time != stop { - t.Error(end.stop, stop) - } - if 0 != len(txndata.spanEvents) { - t.Error(txndata.spanEvents) - } -} - -func TestMultipleChildren(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - t2 := StartSegment(txndata, thread, start.Add(2*time.Second)) - end2, err2 := endSegment(txndata, thread, t2, start.Add(3*time.Second)) - t3 := StartSegment(txndata, thread, start.Add(4*time.Second)) - end3, err3 := endSegment(txndata, thread, t3, start.Add(5*time.Second)) - end1, err1 := endSegment(txndata, thread, t1, start.Add(6*time.Second)) - t4 := StartSegment(txndata, thread, start.Add(7*time.Second)) - end4, err4 := endSegment(txndata, thread, t4, start.Add(8*time.Second)) - - if nil != err1 || end1.duration != 5*time.Second || end1.exclusive != 3*time.Second { - t.Error(end1, err1) - } - if nil != err2 || end2.duration != end2.exclusive || end2.duration != time.Second { - t.Error(end2, err2) - } - if nil != err3 || end3.duration != end3.exclusive || end3.duration != time.Second { - t.Error(end3, err3) - } - if nil != err4 || end4.duration != end4.exclusive || end4.duration != time.Second { - t.Error(end4, err4) - } - if thread.TotalTime() != 7*time.Second { - t.Error(thread.TotalTime()) - } -} - -func TestInvalidStart(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - end, err := endSegment(txndata, thread, SegmentStartTime{}, start.Add(1*time.Second)) - if err != errMalformedSegment { - t.Error(end, err) - } - StartSegment(txndata, thread, start.Add(2*time.Second)) - end, err = endSegment(txndata, thread, SegmentStartTime{}, start.Add(3*time.Second)) - if err != errMalformedSegment { - t.Error(end, err) - } -} - -func TestSegmentAlreadyEnded(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - end, err := endSegment(txndata, thread, t1, start.Add(2*time.Second)) - if err != nil { - t.Error(end, err) - } - end, err = endSegment(txndata, thread, t1, start.Add(3*time.Second)) - if err != errSegmentOrder { - t.Error(end, err) - } -} - -func TestSegmentBadStamp(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - t1.Stamp++ - end, err := endSegment(txndata, thread, t1, start.Add(2*time.Second)) - if err != errSegmentOrder { - t.Error(end, err) - } -} - -func TestSegmentBadDepth(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - t1.Depth++ - end, err := endSegment(txndata, thread, t1, start.Add(2*time.Second)) - if err != errSegmentOrder { - t.Error(end, err) - } -} - -func TestSegmentNegativeDepth(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - t1.Depth = -1 - end, err := endSegment(txndata, thread, t1, start.Add(2*time.Second)) - if err != errMalformedSegment { - t.Error(end, err) - } -} - -func TestSegmentOutOfOrder(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - t2 := StartSegment(txndata, thread, start.Add(2*time.Second)) - t3 := StartSegment(txndata, thread, start.Add(3*time.Second)) - end2, err2 := endSegment(txndata, thread, t2, start.Add(4*time.Second)) - end3, err3 := endSegment(txndata, thread, t3, start.Add(5*time.Second)) - t4 := StartSegment(txndata, thread, start.Add(6*time.Second)) - end4, err4 := endSegment(txndata, thread, t4, start.Add(7*time.Second)) - end1, err1 := endSegment(txndata, thread, t1, start.Add(8*time.Second)) - - if nil != err1 || - end1.duration != 7*time.Second || - end1.exclusive != 4*time.Second { - t.Error(end1, err1) - } - if nil != err2 || end2.duration != end2.exclusive || end2.duration != 2*time.Second { - t.Error(end2, err2) - } - if err3 != errSegmentOrder { - t.Error(end3, err3) - } - if nil != err4 || end4.duration != end4.exclusive || end4.duration != 1*time.Second { - t.Error(end4, err4) - } -} - -// |-t3-| |-t4-| -// |-t2-| |-never-finished---------- -// |-t1-| |--never-finished------------------------ -// |-------alpha------------------------------------------| -// 0 1 2 3 4 5 6 7 8 9 10 11 12 -func TestLostChildren(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - alpha := StartSegment(txndata, thread, start.Add(1*time.Second)) - t1 := StartSegment(txndata, thread, start.Add(2*time.Second)) - EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1") - StartSegment(txndata, thread, start.Add(4*time.Second)) - t2 := StartSegment(txndata, thread, start.Add(5*time.Second)) - EndBasicSegment(txndata, thread, t2, start.Add(6*time.Second), "t2") - StartSegment(txndata, thread, start.Add(7*time.Second)) - t3 := StartSegment(txndata, thread, start.Add(8*time.Second)) - EndBasicSegment(txndata, thread, t3, start.Add(9*time.Second), "t3") - t4 := StartSegment(txndata, thread, start.Add(10*time.Second)) - EndBasicSegment(txndata, thread, t4, start.Add(11*time.Second), "t4") - EndBasicSegment(txndata, thread, alpha, start.Add(12*time.Second), "alpha") - - metrics := newMetricTable(100, time.Now()) - txndata.FinalName = "WebTransaction/Go/zip" - txndata.IsWeb = true - MergeBreakdownMetrics(txndata, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {"Custom/alpha", "", false, []float64{1, 11, 7, 11, 11, 121}}, - {"Custom/t1", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t2", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t3", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t4", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/alpha", txndata.FinalName, false, []float64{1, 11, 7, 11, 11, 121}}, - {"Custom/t1", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t2", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t3", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t4", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - }) -} - -// |-t3-| |-t4-| -// |-t2-| |-never-finished---------- -// |-t1-| |--never-finished------------------------ -// |-------root------------------------------------------------- -// 0 1 2 3 4 5 6 7 8 9 10 11 12 -func TestLostChildrenRoot(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(2*time.Second)) - EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1") - StartSegment(txndata, thread, start.Add(4*time.Second)) - t2 := StartSegment(txndata, thread, start.Add(5*time.Second)) - EndBasicSegment(txndata, thread, t2, start.Add(6*time.Second), "t2") - StartSegment(txndata, thread, start.Add(7*time.Second)) - t3 := StartSegment(txndata, thread, start.Add(8*time.Second)) - EndBasicSegment(txndata, thread, t3, start.Add(9*time.Second), "t3") - t4 := StartSegment(txndata, thread, start.Add(10*time.Second)) - EndBasicSegment(txndata, thread, t4, start.Add(11*time.Second), "t4") - - if thread.TotalTime() != 9*time.Second { - t.Error(thread.TotalTime()) - } - - metrics := newMetricTable(100, time.Now()) - txndata.FinalName = "WebTransaction/Go/zip" - txndata.IsWeb = true - MergeBreakdownMetrics(txndata, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {"Custom/t1", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t2", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t3", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t4", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t1", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t2", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t3", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t4", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - }) -} - -func TestNilSpanEvent(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - - txndata := &TxnData{} - thread := &Thread{} - token := StartSegment(txndata, thread, start) - stop := start.Add(1 * time.Second) - end, err := endSegment(txndata, thread, token, stop) - if nil != err { - t.Error(err) - } - - // A segment without a SpanId does not create a spanEvent. - if evt := end.spanEvent(); evt != nil { - t.Error(evt) - } -} - -func TestDefaultSpanEvent(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - - txndata := &TxnData{} - thread := &Thread{} - token := StartSegment(txndata, thread, start) - stop := start.Add(1 * time.Second) - end, err := endSegment(txndata, thread, token, stop) - if nil != err { - t.Error(err) - } - end.SpanID = "123" - if evt := end.spanEvent(); evt != nil { - if evt.GUID != end.SpanID || - evt.ParentID != end.ParentID || - evt.Timestamp != end.start.Time || - evt.Duration != end.duration || - evt.IsEntrypoint { - t.Error(evt) - } - } -} - -func TestGetRootSpanID(t *testing.T) { - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - if id := txndata.getRootSpanID(); id != "d9466896a525ccbf" { - t.Error(id) - } - if id := txndata.getRootSpanID(); id != "d9466896a525ccbf" { - t.Error(id) - } -} - -func TestCurrentSpanIdentifier(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - thread := &Thread{} - id := txndata.CurrentSpanIdentifier(thread) - if id != "d9466896a525ccbf" { - t.Error(id) - } - - // After starting and ending a segment, the current span id is still the root. - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - _, err1 := endSegment(txndata, thread, t1, start.Add(3*time.Second)) - if nil != err1 { - t.Error(err1) - } - - id = txndata.CurrentSpanIdentifier(thread) - if id != "d9466896a525ccbf" { - t.Error(id) - } - - // After starting a new segment, there should be a new current span id. - StartSegment(txndata, thread, start.Add(2*time.Second)) - id2 := txndata.CurrentSpanIdentifier(thread) - if id2 != "bcfb32e050b264b8" { - t.Error(id2) - } -} - -func TestDatastoreSpanAddress(t *testing.T) { - if s := datastoreSpanAddress("host", "portPathOrID"); s != "host:portPathOrID" { - t.Error(s) - } - if s := datastoreSpanAddress("host", ""); s != "host" { - t.Error(s) - } - if s := datastoreSpanAddress("", ""); s != "" { - t.Error(s) - } -} - -func TestSegmentBasic(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - t2 := StartSegment(txndata, thread, start.Add(2*time.Second)) - EndBasicSegment(txndata, thread, t2, start.Add(3*time.Second), "t2") - EndBasicSegment(txndata, thread, t1, start.Add(4*time.Second), "t1") - t3 := StartSegment(txndata, thread, start.Add(5*time.Second)) - t4 := StartSegment(txndata, thread, start.Add(6*time.Second)) - EndBasicSegment(txndata, thread, t3, start.Add(7*time.Second), "t3") - EndBasicSegment(txndata, thread, t4, start.Add(8*time.Second), "out-of-order") - t5 := StartSegment(txndata, thread, start.Add(9*time.Second)) - EndBasicSegment(txndata, thread, t5, start.Add(10*time.Second), "t1") - - metrics := newMetricTable(100, time.Now()) - txndata.FinalName = "WebTransaction/Go/zip" - txndata.IsWeb = true - MergeBreakdownMetrics(txndata, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {"Custom/t1", "", false, []float64{2, 4, 3, 1, 3, 10}}, - {"Custom/t2", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t3", "", false, []float64{1, 2, 2, 2, 2, 4}}, - {"Custom/t1", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}}, - {"Custom/t2", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - {"Custom/t3", txndata.FinalName, false, []float64{1, 2, 2, 2, 2, 4}}, - }) -} - -func parseURL(raw string) *url.URL { - u, _ := url.Parse(raw) - return u -} - -func TestSegmentExternal(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - t2 := StartSegment(txndata, thread, start.Add(2*time.Second)) - EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t2, - Now: start.Add(3 * time.Second), - Logger: logger.ShimLogger{}, - }) - EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t1, - Now: start.Add(4 * time.Second), - URL: parseURL("http://f1.com"), - Host: "f1", - Logger: logger.ShimLogger{}, - }) - t3 := StartSegment(txndata, thread, start.Add(5*time.Second)) - EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t3, - Now: start.Add(6 * time.Second), - URL: parseURL("http://f1.com"), - Host: "f1", - Logger: logger.ShimLogger{}, - }) - t4 := StartSegment(txndata, thread, start.Add(7*time.Second)) - t4.Stamp++ - EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t4, - Now: start.Add(8 * time.Second), - URL: parseURL("http://invalid-token.com"), - Host: "invalid-token.com", - Logger: logger.ShimLogger{}, - }) - if txndata.externalCallCount != 3 { - t.Error(txndata.externalCallCount) - } - if txndata.externalDuration != 5*time.Second { - t.Error(txndata.externalDuration) - } - metrics := newMetricTable(100, time.Now()) - txndata.FinalName = "WebTransaction/Go/zip" - txndata.IsWeb = true - MergeBreakdownMetrics(txndata, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {"External/all", "", true, []float64{3, 5, 4, 1, 3, 11}}, - {"External/allWeb", "", true, []float64{3, 5, 4, 1, 3, 11}}, - {"External/f1/all", "", false, []float64{2, 4, 3, 1, 3, 10}}, - {"External/unknown/all", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"External/f1/http", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}}, - {"External/unknown/http", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - }) - - metrics = newMetricTable(100, time.Now()) - txndata.FinalName = "OtherTransaction/Go/zip" - txndata.IsWeb = false - MergeBreakdownMetrics(txndata, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {"External/all", "", true, []float64{3, 5, 4, 1, 3, 11}}, - {"External/allOther", "", true, []float64{3, 5, 4, 1, 3, 11}}, - {"External/f1/all", "", false, []float64{2, 4, 3, 1, 3, 10}}, - {"External/unknown/all", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"External/f1/http", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}}, - {"External/unknown/http", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - }) -} - -func TestSegmentDatastore(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - t2 := StartSegment(txndata, thread, start.Add(2*time.Second)) - EndDatastoreSegment(EndDatastoreParams{ - TxnData: txndata, - Thread: thread, - Start: t2, - Now: start.Add(3 * time.Second), - Product: "MySQL", - Operation: "SELECT", - Collection: "my_table", - }) - EndDatastoreSegment(EndDatastoreParams{ - TxnData: txndata, - Thread: thread, - Start: t1, - Now: start.Add(4 * time.Second), - Product: "MySQL", - Operation: "SELECT", - // missing collection - }) - t3 := StartSegment(txndata, thread, start.Add(5*time.Second)) - EndDatastoreSegment(EndDatastoreParams{ - TxnData: txndata, - Thread: thread, - Start: t3, - Now: start.Add(6 * time.Second), - Product: "MySQL", - Operation: "SELECT", - // missing collection - }) - t4 := StartSegment(txndata, thread, start.Add(7*time.Second)) - t4.Stamp++ - EndDatastoreSegment(EndDatastoreParams{ - TxnData: txndata, - Thread: thread, - Start: t4, - Now: start.Add(8 * time.Second), - Product: "MySQL", - Operation: "invalid-token", - }) - t5 := StartSegment(txndata, thread, start.Add(9*time.Second)) - EndDatastoreSegment(EndDatastoreParams{ - TxnData: txndata, - Thread: thread, - Start: t5, - Now: start.Add(10 * time.Second), - // missing datastore, collection, and operation - }) - - if txndata.datastoreCallCount != 4 { - t.Error(txndata.datastoreCallCount) - } - if txndata.datastoreDuration != 6*time.Second { - t.Error(txndata.datastoreDuration) - } - metrics := newMetricTable(100, time.Now()) - txndata.FinalName = "WebTransaction/Go/zip" - txndata.IsWeb = true - MergeBreakdownMetrics(txndata, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {"Datastore/all", "", true, []float64{4, 6, 5, 1, 3, 12}}, - {"Datastore/allWeb", "", true, []float64{4, 6, 5, 1, 3, 12}}, - {"Datastore/MySQL/all", "", true, []float64{3, 5, 4, 1, 3, 11}}, - {"Datastore/MySQL/allWeb", "", true, []float64{3, 5, 4, 1, 3, 11}}, - {"Datastore/Unknown/all", "", true, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/Unknown/allWeb", "", true, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/operation/MySQL/SELECT", "", false, []float64{3, 5, 4, 1, 3, 11}}, - {"Datastore/operation/MySQL/SELECT", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}}, - {"Datastore/operation/Unknown/other", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/operation/Unknown/other", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/statement/MySQL/my_table/SELECT", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/statement/MySQL/my_table/SELECT", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - }) - - metrics = newMetricTable(100, time.Now()) - txndata.FinalName = "OtherTransaction/Go/zip" - txndata.IsWeb = false - MergeBreakdownMetrics(txndata, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {"Datastore/all", "", true, []float64{4, 6, 5, 1, 3, 12}}, - {"Datastore/allOther", "", true, []float64{4, 6, 5, 1, 3, 12}}, - {"Datastore/MySQL/all", "", true, []float64{3, 5, 4, 1, 3, 11}}, - {"Datastore/MySQL/allOther", "", true, []float64{3, 5, 4, 1, 3, 11}}, - {"Datastore/Unknown/all", "", true, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/Unknown/allOther", "", true, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/operation/MySQL/SELECT", "", false, []float64{3, 5, 4, 1, 3, 11}}, - {"Datastore/operation/MySQL/SELECT", txndata.FinalName, false, []float64{2, 4, 3, 1, 3, 10}}, - {"Datastore/operation/Unknown/other", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/operation/Unknown/other", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/statement/MySQL/my_table/SELECT", "", false, []float64{1, 1, 1, 1, 1, 1}}, - {"Datastore/statement/MySQL/my_table/SELECT", txndata.FinalName, false, []float64{1, 1, 1, 1, 1, 1}}, - }) -} - -func TestDatastoreInstancesCrossAgent(t *testing.T) { - var testcases []struct { - Name string `json:"name"` - SystemHostname string `json:"system_hostname"` - DBHostname string `json:"db_hostname"` - Product string `json:"product"` - Port int `json:"port"` - Socket string `json:"unix_socket"` - DatabasePath string `json:"database_path"` - ExpectedMetric string `json:"expected_instance_metric"` - } - - err := crossagent.ReadJSON("datastores/datastore_instances.json", &testcases) - if err != nil { - t.Fatal(err) - } - - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - - for _, tc := range testcases { - portPathOrID := "" - if 0 != tc.Port { - portPathOrID = strconv.Itoa(tc.Port) - } else if "" != tc.Socket { - portPathOrID = tc.Socket - } else if "" != tc.DatabasePath { - portPathOrID = tc.DatabasePath - // These tests makes weird assumptions. - tc.DBHostname = "localhost" - } - - txndata := &TxnData{} - thread := &Thread{} - - s := StartSegment(txndata, thread, start) - EndDatastoreSegment(EndDatastoreParams{ - Thread: thread, - TxnData: txndata, - Start: s, - Now: start.Add(1 * time.Second), - Product: tc.Product, - Operation: "SELECT", - Collection: "my_table", - PortPathOrID: portPathOrID, - Host: tc.DBHostname, - }) - - expect := strings.Replace(tc.ExpectedMetric, - tc.SystemHostname, ThisHost, -1) - - metrics := newMetricTable(100, time.Now()) - txndata.FinalName = "OtherTransaction/Go/zip" - txndata.IsWeb = false - MergeBreakdownMetrics(txndata, metrics) - data := []float64{1, 1, 1, 1, 1, 1} - ExpectMetrics(ExtendValidator(t, tc.Name), metrics, []WantMetric{ - {"Datastore/all", "", true, data}, - {"Datastore/allOther", "", true, data}, - {"Datastore/" + tc.Product + "/all", "", true, data}, - {"Datastore/" + tc.Product + "/allOther", "", true, data}, - {"Datastore/operation/" + tc.Product + "/SELECT", "", false, data}, - {"Datastore/statement/" + tc.Product + "/my_table/SELECT", "", false, data}, - {"Datastore/statement/" + tc.Product + "/my_table/SELECT", txndata.FinalName, false, data}, - {expect, "", false, data}, - }) - } -} - -func TestGenericSpanEventCreation(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - thread := &Thread{} - - // Enable that which is necessary to generate span events when segments are ended. - txndata.LazilyCalculateSampled = func() bool { return true } - txndata.SpanEventsEnabled = true - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1") - - // Since a basic segment has just ended, there should be exactly one generic span event in txndata.spanEvents[] - if 1 != len(txndata.spanEvents) { - t.Error(txndata.spanEvents) - } - if txndata.spanEvents[0].Category != spanCategoryGeneric { - t.Error(txndata.spanEvents[0].Category) - } -} - -func TestSpanEventNotSampled(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - thread := &Thread{} - - txndata.LazilyCalculateSampled = func() bool { return false } - txndata.SpanEventsEnabled = true - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1") - - if 0 != len(txndata.spanEvents) { - t.Error(txndata.spanEvents) - } -} - -func TestSpanEventNotEnabled(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - thread := &Thread{} - - txndata.LazilyCalculateSampled = func() bool { return true } - txndata.SpanEventsEnabled = false - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - EndBasicSegment(txndata, thread, t1, start.Add(3*time.Second), "t1") - - if 0 != len(txndata.spanEvents) { - t.Error(txndata.spanEvents) - } -} - -func TestDatastoreSpanEventCreation(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - thread := &Thread{} - - // Enable that which is necessary to generate span events when segments are ended. - txndata.LazilyCalculateSampled = func() bool { return true } - txndata.SpanEventsEnabled = true - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - EndDatastoreSegment(EndDatastoreParams{ - TxnData: txndata, - Thread: thread, - Start: t1, - Now: start.Add(3 * time.Second), - Product: "MySQL", - Operation: "SELECT", - Collection: "my_table", - }) - - // Since a datastore segment has just ended, there should be exactly one datastore span event in txndata.spanEvents[] - if 1 != len(txndata.spanEvents) { - t.Error(txndata.spanEvents) - } - if txndata.spanEvents[0].Category != spanCategoryDatastore { - t.Error(txndata.spanEvents[0].Category) - } -} - -func TestHTTPSpanEventCreation(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - thread := &Thread{} - - // Enable that which is necessary to generate span events when segments are ended. - txndata.LazilyCalculateSampled = func() bool { return true } - txndata.SpanEventsEnabled = true - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t1, - Now: start.Add(3 * time.Second), - URL: nil, - Logger: logger.ShimLogger{}, - }) - - // Since an external segment has just ended, there should be exactly one HTTP span event in txndata.spanEvents[] - if 1 != len(txndata.spanEvents) { - t.Error(txndata.spanEvents) - } - if txndata.spanEvents[0].Category != spanCategoryHTTP { - t.Error(txndata.spanEvents[0].Category) - } -} - -func TestExternalSegmentCAT(t *testing.T) { - // Test that when the reading the response CAT headers fails, an external - // segment is still created. - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - txndata.CrossProcess.Enabled = true - thread := &Thread{} - - resp := &http.Response{Header: http.Header{}} - resp.Header.Add(cat.NewRelicAppDataName, "bad header value") - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - err := EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t1, - Now: start.Add(4 * time.Second), - URL: parseURL("http://f1.com"), - Logger: logger.ShimLogger{}, - }) - - if nil != err { - t.Error("EndExternalSegment returned an err:", err) - } - if txndata.externalCallCount != 1 { - t.Error(txndata.externalCallCount) - } - if txndata.externalDuration != 3*time.Second { - t.Error(txndata.externalDuration) - } - - metrics := newMetricTable(100, time.Now()) - txndata.FinalName = "OtherTransaction/Go/zip" - txndata.IsWeb = false - MergeBreakdownMetrics(txndata, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {"External/all", "", true, []float64{1, 3, 3, 3, 3, 9}}, - {"External/allOther", "", true, []float64{1, 3, 3, 3, 3, 9}}, - {"External/f1.com/all", "", false, []float64{1, 3, 3, 3, 3, 9}}, - {"External/f1.com/http", txndata.FinalName, false, []float64{1, 3, 3, 3, 3, 9}}, - }) -} - -func TestEndMessageSegment(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - txndata.CrossProcess.Enabled = true - txndata.LazilyCalculateSampled = func() bool { return true } - txndata.SpanEventsEnabled = true - thread := &Thread{} - - seg1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - seg2 := StartSegment(txndata, thread, start.Add(2*time.Second)) - EndMessageSegment(EndMessageParams{ - TxnData: txndata, - Thread: thread, - Start: seg1, - Now: start.Add(3 * time.Second), - Logger: nil, - DestinationName: "MyTopic", - Library: "Kafka", - DestinationType: "Topic", - }) - EndMessageSegment(EndMessageParams{ - TxnData: txndata, - Thread: thread, - Start: seg2, - Now: start.Add(4 * time.Second), - Logger: nil, - DestinationName: "MyOtherTopic", - Library: "Kafka", - DestinationType: "Topic", - }) - - metrics := newMetricTable(100, time.Now()) - txndata.FinalName = "WebTransaction/Go/zip" - txndata.IsWeb = true - MergeBreakdownMetrics(txndata, metrics) - ExpectMetrics(t, metrics, []WantMetric{ - {"MessageBroker/Kafka/Topic/Produce/Named/MyTopic", "WebTransaction/Go/zip", false, []float64{1, 2, 2, 2, 2, 4}}, - {"MessageBroker/Kafka/Topic/Produce/Named/MyTopic", "", false, []float64{1, 2, 2, 2, 2, 4}}, - }) -} diff --git a/internal/txn_cross_process.go b/internal/txn_cross_process.go deleted file mode 100644 index 3c101f720..000000000 --- a/internal/txn_cross_process.go +++ /dev/null @@ -1,420 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "errors" - "fmt" - "time" - - "github.com/newrelic/go-agent/internal/cat" -) - -// Bitfield values for the TxnCrossProcess.Type field. -const ( - txnCrossProcessSynthetics = (1 << 0) - txnCrossProcessInbound = (1 << 1) - txnCrossProcessOutbound = (1 << 2) -) - -var ( - // ErrAccountNotTrusted indicates that, while the inbound headers were valid, - // the account ID within them is not trusted by the user's application. - ErrAccountNotTrusted = errors.New("account not trusted") -) - -// TxnCrossProcess contains the metadata required for CAT and Synthetics -// headers, transaction events, and traces. -type TxnCrossProcess struct { - // The user side switch controlling whether CAT is enabled or not. - Enabled bool - - // The user side switch controlling whether Distributed Tracing is enabled or not - // This is required by synthetics support. If Distributed Tracing is enabled, - // any synthetics functionality that is triggered should not set nr.guid. - DistributedTracingEnabled bool - - // Rather than copying in the entire ConnectReply, here are the fields that - // we need to support CAT. - CrossProcessID []byte - EncodingKey []byte - TrustedAccounts trustedAccountSet - - // CAT state for a given transaction. - Type uint8 - ClientID string - GUID string - TripID string - PathHash string - AlternatePathHashes map[string]bool - ReferringPathHash string - ReferringTxnGUID string - Synthetics *cat.SyntheticsHeader - - // The encoded synthetics header received as part of the request headers, if - // any. By storing this here, we avoid needing to marshal the invariant - // Synthetics struct above each time an external segment is created. - SyntheticsHeader string -} - -// CrossProcessMetadata represents the metadata that must be transmitted with -// an external request for CAT to work. -type CrossProcessMetadata struct { - ID string - TxnData string - Synthetics string -} - -// Init initialises a TxnCrossProcess based on the given application connect -// reply. -func (txp *TxnCrossProcess) Init(enabled bool, dt bool, reply *ConnectReply) { - txp.CrossProcessID = []byte(reply.CrossProcessID) - txp.EncodingKey = []byte(reply.EncodingKey) - txp.DistributedTracingEnabled = dt - txp.Enabled = enabled - txp.TrustedAccounts = reply.TrustedAccounts -} - -// CreateCrossProcessMetadata generates request metadata that enable CAT and -// Synthetics support for an external segment. -func (txp *TxnCrossProcess) CreateCrossProcessMetadata(txnName, appName string) (CrossProcessMetadata, error) { - metadata := CrossProcessMetadata{} - - // Regardless of the user's CAT settings, if there was a synthetics header in - // the inbound request, a synthetics header should always be included in the - // outbound request headers. - if txp.IsSynthetics() { - metadata.Synthetics = txp.SyntheticsHeader - } - - if txp.Enabled { - txp.SetOutbound(true) - txp.requireTripID() - - id, err := txp.outboundID() - if err != nil { - return metadata, err - } - - txnData, err := txp.outboundTxnData(txnName, appName) - if err != nil { - return metadata, err - } - - metadata.ID = id - metadata.TxnData = txnData - } - - return metadata, nil -} - -// Finalise handles any end-of-transaction tasks. In practice, this simply -// means ensuring the path hash is set if it hasn't already been. -func (txp *TxnCrossProcess) Finalise(txnName, appName string) error { - if txp.Enabled && txp.Used() { - _, err := txp.setPathHash(txnName, appName) - return err - } - - // If there was no CAT activity, then do nothing, successfully. - return nil -} - -// IsInbound returns true if the transaction had inbound CAT headers. -func (txp *TxnCrossProcess) IsInbound() bool { - return 0 != (txp.Type & txnCrossProcessInbound) -} - -// IsOutbound returns true if the transaction has generated outbound CAT -// headers. -func (txp *TxnCrossProcess) IsOutbound() bool { - // We don't actually use this anywhere today, but it feels weird not having - // it. - return 0 != (txp.Type & txnCrossProcessOutbound) -} - -// IsSynthetics returns true if the transaction had inbound Synthetics headers. -func (txp *TxnCrossProcess) IsSynthetics() bool { - // Technically, this is redundant: the presence of a non-nil Synthetics - // pointer should be sufficient to determine if this is a synthetics - // transaction. Nevertheless, it's convenient to have the Type field be - // non-zero if any CAT behaviour has occurred. - return 0 != (txp.Type&txnCrossProcessSynthetics) && nil != txp.Synthetics -} - -// ParseAppData decodes the given appData value. -func (txp *TxnCrossProcess) ParseAppData(encodedAppData string) (*cat.AppDataHeader, error) { - if !txp.Enabled { - return nil, nil - } - if encodedAppData != "" { - rawAppData, err := Deobfuscate(encodedAppData, txp.EncodingKey) - if err != nil { - return nil, err - } - - appData := &cat.AppDataHeader{} - if err := json.Unmarshal(rawAppData, appData); err != nil { - return nil, err - } - - return appData, nil - } - - return nil, nil -} - -// CreateAppData creates the appData value that should be sent with a response -// to ensure CAT operates as expected. -func (txp *TxnCrossProcess) CreateAppData(name string, queueTime, responseTime time.Duration, contentLength int64) (string, error) { - // If CAT is disabled, do nothing, successfully. - if !txp.Enabled { - return "", nil - } - - data, err := json.Marshal(&cat.AppDataHeader{ - CrossProcessID: string(txp.CrossProcessID), - TransactionName: name, - QueueTimeInSeconds: queueTime.Seconds(), - ResponseTimeInSeconds: responseTime.Seconds(), - ContentLength: contentLength, - TransactionGUID: txp.GUID, - }) - if err != nil { - return "", err - } - - obfuscated, err := Obfuscate(data, txp.EncodingKey) - if err != nil { - return "", err - } - - return obfuscated, nil -} - -// Used returns true if any CAT or Synthetics related functionality has been -// triggered on the transaction. -func (txp *TxnCrossProcess) Used() bool { - return 0 != txp.Type -} - -// SetInbound sets the inbound CAT flag. This function is provided only for -// internal and unit testing purposes, and should not be used outside of this -// package normally. -func (txp *TxnCrossProcess) SetInbound(inbound bool) { - if inbound { - txp.Type |= txnCrossProcessInbound - } else { - txp.Type &^= txnCrossProcessInbound - } -} - -// SetOutbound sets the outbound CAT flag. This function is provided only for -// internal and unit testing purposes, and should not be used outside of this -// package normally. -func (txp *TxnCrossProcess) SetOutbound(outbound bool) { - if outbound { - txp.Type |= txnCrossProcessOutbound - } else { - txp.Type &^= txnCrossProcessOutbound - } -} - -// SetSynthetics sets the Synthetics CAT flag. This function is provided only -// for internal and unit testing purposes, and should not be used outside of -// this package normally. -func (txp *TxnCrossProcess) SetSynthetics(synthetics bool) { - if synthetics { - txp.Type |= txnCrossProcessSynthetics - } else { - txp.Type &^= txnCrossProcessSynthetics - } -} - -// handleInboundRequestHeaders parses the CAT headers from the given metadata -// and updates the relevant fields on the provided TxnData. -func (txp *TxnCrossProcess) handleInboundRequestHeaders(metadata CrossProcessMetadata) error { - if txp.Enabled && metadata.ID != "" && metadata.TxnData != "" { - if err := txp.handleInboundRequestEncodedCAT(metadata.ID, metadata.TxnData); err != nil { - return err - } - } - - if metadata.Synthetics != "" { - if err := txp.handleInboundRequestEncodedSynthetics(metadata.Synthetics); err != nil { - return err - } - } - - return nil -} - -func (txp *TxnCrossProcess) handleInboundRequestEncodedCAT(encodedID, encodedTxnData string) error { - rawID, err := Deobfuscate(encodedID, txp.EncodingKey) - if err != nil { - return err - } - - rawTxnData, err := Deobfuscate(encodedTxnData, txp.EncodingKey) - if err != nil { - return err - } - - if err := txp.handleInboundRequestID(rawID); err != nil { - return err - } - - return txp.handleInboundRequestTxnData(rawTxnData) -} - -func (txp *TxnCrossProcess) handleInboundRequestID(raw []byte) error { - id, err := cat.NewIDHeader(raw) - if err != nil { - return err - } - - if !txp.TrustedAccounts.IsTrusted(id.AccountID) { - return ErrAccountNotTrusted - } - - txp.SetInbound(true) - txp.ClientID = string(raw) - txp.setRequireGUID() - - return nil -} - -func (txp *TxnCrossProcess) handleInboundRequestTxnData(raw []byte) error { - txnData := &cat.TxnDataHeader{} - if err := json.Unmarshal(raw, txnData); err != nil { - return err - } - - txp.SetInbound(true) - if txnData.TripID != "" { - txp.TripID = txnData.TripID - } else { - txp.setRequireGUID() - txp.TripID = txp.GUID - } - txp.ReferringTxnGUID = txnData.GUID - txp.ReferringPathHash = txnData.PathHash - - return nil -} - -func (txp *TxnCrossProcess) handleInboundRequestEncodedSynthetics(encoded string) error { - raw, err := Deobfuscate(encoded, txp.EncodingKey) - if err != nil { - return err - } - - if err := txp.handleInboundRequestSynthetics(raw); err != nil { - return err - } - - txp.SyntheticsHeader = encoded - return nil -} - -func (txp *TxnCrossProcess) handleInboundRequestSynthetics(raw []byte) error { - synthetics := &cat.SyntheticsHeader{} - if err := json.Unmarshal(raw, synthetics); err != nil { - return err - } - - // The specced behaviour here if the account isn't trusted is to disable the - // synthetics handling, but not CAT in general, so we won't return an error - // here. - if txp.TrustedAccounts.IsTrusted(synthetics.AccountID) { - txp.SetSynthetics(true) - txp.setRequireGUID() - txp.Synthetics = synthetics - } - - return nil -} - -func (txp *TxnCrossProcess) outboundID() (string, error) { - return Obfuscate(txp.CrossProcessID, txp.EncodingKey) -} - -func (txp *TxnCrossProcess) outboundTxnData(txnName, appName string) (string, error) { - pathHash, err := txp.setPathHash(txnName, appName) - if err != nil { - return "", err - } - - data, err := json.Marshal(&cat.TxnDataHeader{ - GUID: txp.GUID, - TripID: txp.TripID, - PathHash: pathHash, - }) - if err != nil { - return "", err - } - - return Obfuscate(data, txp.EncodingKey) -} - -// setRequireGUID ensures that the transaction has a valid GUID, and sets the -// nr.guid and trip ID if they are not already set. If the customer has enabled -// DistributedTracing, then the new style of guid will be set elsewhere. -func (txp *TxnCrossProcess) setRequireGUID() { - if txp.DistributedTracingEnabled { - return - } - - if txp.GUID != "" { - return - } - - txp.GUID = fmt.Sprintf("%x", RandUint64()) - - if txp.TripID == "" { - txp.requireTripID() - } -} - -// requireTripID ensures that the transaction has a valid trip ID. -func (txp *TxnCrossProcess) requireTripID() { - if !txp.Enabled { - return - } - if txp.TripID != "" { - return - } - - txp.setRequireGUID() - txp.TripID = txp.GUID -} - -// setPathHash generates a path hash, sets the transaction's path hash to -// match, and returns it. This function will also ensure that the alternate -// path hashes are correctly updated. -func (txp *TxnCrossProcess) setPathHash(txnName, appName string) (string, error) { - pathHash, err := cat.GeneratePathHash(txp.ReferringPathHash, txnName, appName) - if err != nil { - return "", err - } - - if pathHash != txp.PathHash { - if txp.PathHash != "" { - // Lazily initialise the alternate path hashes if they haven't been - // already. - if txp.AlternatePathHashes == nil { - txp.AlternatePathHashes = make(map[string]bool) - } - - // The spec limits us to a maximum of 10 alternate path hashes. - if len(txp.AlternatePathHashes) < 10 { - txp.AlternatePathHashes[txp.PathHash] = true - } - } - txp.PathHash = pathHash - } - - return pathHash, nil -} diff --git a/internal/txn_cross_process_test.go b/internal/txn_cross_process_test.go deleted file mode 100644 index a100e5324..000000000 --- a/internal/txn_cross_process_test.go +++ /dev/null @@ -1,822 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "fmt" - "net/http" - "reflect" - "testing" - "time" - - "github.com/newrelic/go-agent/internal/cat" -) - -var ( - replyAccountOne = &ConnectReply{ - CrossProcessID: "1#1", - EncodingKey: "foo", - TrustedAccounts: map[int]struct{}{1: {}}, - } - - replyAccountTwo = &ConnectReply{ - CrossProcessID: "2#2", - EncodingKey: "foo", - TrustedAccounts: map[int]struct{}{2: {}}, - } - - requestEmpty = newRequest().Request - requestCATOne = newRequest().withCAT(newTxnCrossProcessFromConnectReply(replyAccountOne), "txn", "app").Request - requestSyntheticsOne = newRequest().withSynthetics(1, "foo").Request - requestCATSyntheticsOne = newRequest().withCAT(newTxnCrossProcessFromConnectReply(replyAccountOne), "txn", "app").withSynthetics(1, "foo").Request -) - -func mustObfuscate(input, encodingKey string) string { - output, err := Obfuscate([]byte(input), []byte(encodingKey)) - if err != nil { - panic(err) - } - - return string(output) -} - -func newTxnCrossProcessFromConnectReply(reply *ConnectReply) *TxnCrossProcess { - txp := &TxnCrossProcess{GUID: "abcdefgh"} - txp.Init(true, false, reply) - - return txp -} - -type request struct { - *http.Request -} - -func newRequest() *request { - req, err := http.NewRequest("GET", "http://foo.bar", nil) - if err != nil { - panic(err) - } - - return &request{Request: req} -} - -func (req *request) withCAT(txp *TxnCrossProcess, txnName, appName string) *request { - metadata, err := txp.CreateCrossProcessMetadata(txnName, appName) - if err != nil { - panic(err) - } - - for k, values := range MetadataToHTTPHeader(metadata) { - for _, v := range values { - req.Header.Add(k, v) - } - } - - return req -} - -func (req *request) withSynthetics(account int, encodingKey string) *request { - header := fmt.Sprintf(`[1,%d,"resource","job","monitor"]`, account) - obfuscated, err := Obfuscate([]byte(header), []byte(encodingKey)) - if err != nil { - panic(err) - } - - req.Header.Add(cat.NewRelicSyntheticsName, string(obfuscated)) - return req -} - -func TestTxnCrossProcessInit(t *testing.T) { - for _, tc := range []struct { - name string - enabled bool - reply *ConnectReply - req *http.Request - expected *TxnCrossProcess - expectedError bool - }{ - { - name: "disabled", - enabled: false, - reply: replyAccountOne, - req: nil, - expected: &TxnCrossProcess{ - CrossProcessID: []byte("1#1"), - EncodingKey: []byte("foo"), - Enabled: false, - TrustedAccounts: map[int]struct{}{1: {}}, - }, - expectedError: false, - }, - { - name: "normal connect reply without a request", - enabled: true, - reply: replyAccountOne, - req: nil, - expected: &TxnCrossProcess{ - CrossProcessID: []byte("1#1"), - EncodingKey: []byte("foo"), - Enabled: true, - TrustedAccounts: map[int]struct{}{1: {}}, - }, - expectedError: false, - }, - { - name: "normal connect reply with a request without headers", - enabled: true, - reply: replyAccountOne, - req: requestEmpty, - expected: &TxnCrossProcess{ - CrossProcessID: []byte("1#1"), - EncodingKey: []byte("foo"), - Enabled: true, - TrustedAccounts: map[int]struct{}{1: {}}, - }, - expectedError: false, - }, - { - name: "normal connect reply with a request with untrusted headers", - enabled: true, - reply: replyAccountTwo, - req: requestCATOne, - expected: &TxnCrossProcess{ - CrossProcessID: []byte("2#2"), - EncodingKey: []byte("foo"), - Enabled: true, - TrustedAccounts: map[int]struct{}{2: {}}, - }, - expectedError: true, - }, - { - name: "normal connect reply with a request with trusted headers", - enabled: true, - reply: replyAccountOne, - req: requestCATOne, - expected: &TxnCrossProcess{ - CrossProcessID: []byte("1#1"), - EncodingKey: []byte("foo"), - Enabled: true, - TrustedAccounts: map[int]struct{}{1: {}}, - }, - expectedError: false, - }, - } { - actual := &TxnCrossProcess{} - - id := "" - txnData := "" - synthetics := "" - if tc.req != nil { - id = tc.req.Header.Get(cat.NewRelicIDName) - txnData = tc.req.Header.Get(cat.NewRelicTxnName) - synthetics = tc.req.Header.Get(cat.NewRelicSyntheticsName) - } - - actual.Init(tc.enabled, false, tc.reply) - err := actual.handleInboundRequestHeaders(CrossProcessMetadata{id, txnData, synthetics}) - - if tc.expectedError == false && err != nil { - t.Errorf("%s: unexpected error returned from Init: %v", tc.name, err) - } else if tc.expectedError && err == nil { - t.Errorf("%s: no error returned from Init when one was expected", tc.name) - } - - if !reflect.DeepEqual(actual.EncodingKey, tc.expected.EncodingKey) { - t.Errorf("%s: EncodingKey mismatch: expected=%v; got=%v", tc.name, tc.expected.EncodingKey, actual.EncodingKey) - } - - if !reflect.DeepEqual(actual.CrossProcessID, tc.expected.CrossProcessID) { - t.Errorf("%s: CrossProcessID mismatch: expected=%v; got=%v", tc.name, tc.expected.CrossProcessID, actual.CrossProcessID) - } - - if !reflect.DeepEqual(actual.TrustedAccounts, tc.expected.TrustedAccounts) { - t.Errorf("%s: TrustedAccounts mismatch: expected=%v; got=%v", tc.name, tc.expected.TrustedAccounts, actual.TrustedAccounts) - } - - if actual.Enabled != tc.expected.Enabled { - t.Errorf("%s: Enabled mismatch: expected=%v; got=%v", tc.name, tc.expected.Enabled, actual.Enabled) - } - } -} - -func TestTxnCrossProcessCreateCrossProcessMetadata(t *testing.T) { - for _, tc := range []struct { - name string - enabled bool - reply *ConnectReply - req *http.Request - txnName string - appName string - expectedError bool - expectedMetadata CrossProcessMetadata - }{ - { - name: "disabled, no header", - enabled: false, - reply: replyAccountOne, - req: nil, - txnName: "txn", - appName: "app", - expectedError: false, - expectedMetadata: CrossProcessMetadata{}, - }, - { - name: "disabled, header", - enabled: false, - reply: replyAccountOne, - req: requestCATOne, - txnName: "txn", - appName: "app", - expectedError: false, - expectedMetadata: CrossProcessMetadata{}, - }, - { - name: "disabled, synthetics", - enabled: false, - reply: replyAccountOne, - req: requestSyntheticsOne, - txnName: "txn", - appName: "app", - expectedError: false, - expectedMetadata: CrossProcessMetadata{ - Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"), - }, - }, - { - name: "disabled, header, synthetics", - enabled: false, - reply: replyAccountOne, - req: requestCATSyntheticsOne, - txnName: "txn", - appName: "app", - expectedError: false, - expectedMetadata: CrossProcessMetadata{ - Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"), - }, - }, - { - name: "enabled, no header, no synthetics", - enabled: true, - reply: replyAccountOne, - req: requestEmpty, - txnName: "txn", - appName: "app", - expectedError: false, - expectedMetadata: CrossProcessMetadata{ - ID: mustObfuscate(`1#1`, "foo"), - TxnData: mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"), - }, - }, - { - name: "enabled, no header, synthetics", - enabled: true, - reply: replyAccountOne, - req: requestSyntheticsOne, - txnName: "txn", - appName: "app", - expectedError: false, - expectedMetadata: CrossProcessMetadata{ - ID: mustObfuscate(`1#1`, "foo"), - TxnData: mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"), - Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"), - }, - }, - { - name: "enabled, header, no synthetics", - enabled: true, - reply: replyAccountOne, - req: requestCATOne, - txnName: "txn", - appName: "app", - expectedError: false, - expectedMetadata: CrossProcessMetadata{ - ID: mustObfuscate(`1#1`, "foo"), - TxnData: mustObfuscate(`["00000000",false,"abcdefgh","cbec2654"]`, "foo"), - }, - }, - { - name: "enabled, header, synthetics", - enabled: true, - reply: replyAccountOne, - req: requestCATSyntheticsOne, - txnName: "txn", - appName: "app", - expectedError: false, - expectedMetadata: CrossProcessMetadata{ - ID: mustObfuscate(`1#1`, "foo"), - TxnData: mustObfuscate(`["00000000",false,"abcdefgh","cbec2654"]`, "foo"), - Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"), - }, - }, - } { - txp := &TxnCrossProcess{GUID: "00000000"} - txp.Init(tc.enabled, false, tc.reply) - if nil != tc.req { - txp.InboundHTTPRequest(tc.req.Header) - } - metadata, err := txp.CreateCrossProcessMetadata(tc.txnName, tc.appName) - - if tc.expectedError == false && err != nil { - t.Errorf("%s: unexpected error returned from CreateCrossProcessMetadata: %v", tc.name, err) - } else if tc.expectedError && err == nil { - t.Errorf("%s: no error returned from CreateCrossProcessMetadata when one was expected", tc.name) - } - - if !reflect.DeepEqual(tc.expectedMetadata, metadata) { - t.Errorf("%s: metadata mismatch: expected=%v; got=%v", tc.name, tc.expectedMetadata, metadata) - } - - // Ensure that a path hash was generated if TxnData was created. - if metadata.TxnData != "" && txp.PathHash == "" { - t.Errorf("%s: no path hash generated", tc.name) - } - } -} - -func TestTxnCrossProcessCreateCrossProcessMetadataError(t *testing.T) { - // Ensure errors bubble back up from deeper within our obfuscation code. - // It's likely impossible to get outboundTxnData() to fail, but we can get - // outboundID() to fail by having an empty encoding key. - txp := &TxnCrossProcess{Enabled: true} - metadata, err := txp.CreateCrossProcessMetadata("txn", "app") - if metadata.ID != "" || metadata.TxnData != "" || metadata.Synthetics != "" { - t.Errorf("one or more metadata fields were set unexpectedly; got %v", metadata) - } - if err == nil { - t.Errorf("did not get expected error with an empty encoding key") - } - - // Test the above with Synthetics support to ensure that the Synthetics - // payload is still set. - txp = &TxnCrossProcess{ - Enabled: true, - Type: txnCrossProcessSynthetics, - SyntheticsHeader: "foo", - // This won't be actually examined, but can't be nil for the IsSynthetics() - // check to pass. - Synthetics: &cat.SyntheticsHeader{}, - } - metadata, err = txp.CreateCrossProcessMetadata("txn", "app") - if metadata.ID != "" || metadata.TxnData != "" { - t.Errorf("one or more metadata fields were set unexpectedly; got %v", metadata) - } - if metadata.Synthetics != "foo" { - t.Errorf("unexpected synthetics metadata: expected %s; got %s", "foo", metadata.Synthetics) - } - if err == nil { - t.Errorf("did not get expected error with an empty encoding key") - } -} - -func TestTxnCrossProcessFinalise(t *testing.T) { - // No CAT. - txp := &TxnCrossProcess{} - txp.Init(true, false, replyAccountOne) - if err := txp.Finalise("txn", "app"); err != nil { - t.Errorf("unexpected error: %v", err) - } - if txp.PathHash != "" { - t.Errorf("unexpected path hash: %s", txp.PathHash) - } - - // CAT, but no path hash. - txp = &TxnCrossProcess{} - txp.Init(true, false, replyAccountOne) - txp.InboundHTTPRequest(requestCATOne.Header) - if txp.PathHash != "" { - t.Errorf("unexpected path hash: %s", txp.PathHash) - } - if err := txp.Finalise("txn", "app"); err != nil { - t.Errorf("unexpected error: %v", err) - } - if txp.PathHash == "" { - t.Error("unexpected lack of path hash") - } - - // CAT, with a path hash. - txp = &TxnCrossProcess{} - txp.Init(true, false, replyAccountOne) - txp.InboundHTTPRequest(requestCATOne.Header) - txp.CreateCrossProcessMetadata("txn", "app") - if txp.PathHash == "" { - t.Error("unexpected lack of path hash") - } - if err := txp.Finalise("txn", "app"); err != nil { - t.Errorf("unexpected error: %v", err) - } - if txp.PathHash == "" { - t.Error("unexpected lack of path hash") - } -} - -func TestTxnCrossProcessIsInbound(t *testing.T) { - for _, tc := range []struct { - txpType uint8 - expected bool - }{ - {0, false}, - {txnCrossProcessSynthetics, false}, - {txnCrossProcessInbound, true}, - {txnCrossProcessOutbound, false}, - {txnCrossProcessSynthetics | txnCrossProcessInbound, true}, - {txnCrossProcessSynthetics | txnCrossProcessOutbound, false}, - {txnCrossProcessInbound | txnCrossProcessOutbound, true}, - {txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, true}, - } { - txp := &TxnCrossProcess{Type: tc.txpType} - actual := txp.IsInbound() - if actual != tc.expected { - t.Errorf("unexpected IsInbound result for input %d: expected=%v; got=%v", tc.txpType, tc.expected, actual) - } - } -} - -func TestTxnCrossProcessIsOutbound(t *testing.T) { - for _, tc := range []struct { - txpType uint8 - expected bool - }{ - {0, false}, - {txnCrossProcessSynthetics, false}, - {txnCrossProcessInbound, false}, - {txnCrossProcessOutbound, true}, - {txnCrossProcessSynthetics | txnCrossProcessInbound, false}, - {txnCrossProcessSynthetics | txnCrossProcessOutbound, true}, - {txnCrossProcessInbound | txnCrossProcessOutbound, true}, - {txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, true}, - } { - txp := &TxnCrossProcess{Type: tc.txpType} - actual := txp.IsOutbound() - if actual != tc.expected { - t.Errorf("unexpected IsOutbound result for input %d: expected=%v; got=%v", tc.txpType, tc.expected, actual) - } - } -} - -func TestTxnCrossProcessIsSynthetics(t *testing.T) { - for _, tc := range []struct { - txpType uint8 - synthetics *cat.SyntheticsHeader - expected bool - }{ - {0, nil, false}, - {txnCrossProcessSynthetics, nil, false}, - {txnCrossProcessInbound, nil, false}, - {txnCrossProcessOutbound, nil, false}, - {txnCrossProcessSynthetics | txnCrossProcessInbound, nil, false}, - {txnCrossProcessSynthetics | txnCrossProcessOutbound, nil, false}, - {txnCrossProcessInbound | txnCrossProcessOutbound, nil, false}, - {txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, nil, false}, - {0, &cat.SyntheticsHeader{}, false}, - {txnCrossProcessSynthetics, &cat.SyntheticsHeader{}, true}, - {txnCrossProcessInbound, &cat.SyntheticsHeader{}, false}, - {txnCrossProcessOutbound, &cat.SyntheticsHeader{}, false}, - {txnCrossProcessSynthetics | txnCrossProcessInbound, &cat.SyntheticsHeader{}, true}, - {txnCrossProcessSynthetics | txnCrossProcessOutbound, &cat.SyntheticsHeader{}, true}, - {txnCrossProcessInbound | txnCrossProcessOutbound, &cat.SyntheticsHeader{}, false}, - {txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, &cat.SyntheticsHeader{}, true}, - } { - txp := &TxnCrossProcess{Type: tc.txpType, Synthetics: tc.synthetics} - actual := txp.IsSynthetics() - if actual != tc.expected { - t.Errorf("unexpected IsSynthetics result for input %d and %p: expected=%v; got=%v", tc.txpType, tc.synthetics, tc.expected, actual) - } - } -} - -func TestTxnCrossProcessUsed(t *testing.T) { - for _, tc := range []struct { - txpType uint8 - expected bool - }{ - {0, false}, - {txnCrossProcessSynthetics, true}, - {txnCrossProcessInbound, true}, - {txnCrossProcessOutbound, true}, - {txnCrossProcessSynthetics | txnCrossProcessInbound, true}, - {txnCrossProcessSynthetics | txnCrossProcessOutbound, true}, - {txnCrossProcessInbound | txnCrossProcessOutbound, true}, - {txnCrossProcessSynthetics | txnCrossProcessInbound | txnCrossProcessOutbound, true}, - } { - txp := &TxnCrossProcess{Type: tc.txpType} - actual := txp.Used() - if actual != tc.expected { - t.Errorf("unexpected Used result for input %d: expected=%v; got=%v", tc.txpType, tc.expected, actual) - } - } -} - -func TestTxnCrossProcessSetInbound(t *testing.T) { - txp := &TxnCrossProcess{Type: 0} - - txp.SetInbound(false) - if txp.IsInbound() != false { - t.Error("Inbound is not false after being set to false from false") - } - - txp.SetInbound(true) - if txp.IsInbound() != true { - t.Error("Inbound is not true after being set to true from false") - } - - txp.SetInbound(true) - if txp.IsInbound() != true { - t.Error("Inbound is not true after being set to true from true") - } - - txp.SetInbound(false) - if txp.IsInbound() != false { - t.Error("Inbound is not false after being set to false from true") - } -} - -func TestTxnCrossProcessSetOutbound(t *testing.T) { - txp := &TxnCrossProcess{Type: 0} - - txp.SetOutbound(false) - if txp.IsOutbound() != false { - t.Error("Outbound is not false after being set to false from false") - } - - txp.SetOutbound(true) - if txp.IsOutbound() != true { - t.Error("Outbound is not true after being set to true from false") - } - - txp.SetOutbound(true) - if txp.IsOutbound() != true { - t.Error("Outbound is not true after being set to true from true") - } - - txp.SetOutbound(false) - if txp.IsOutbound() != false { - t.Error("Outbound is not false after being set to false from true") - } -} - -func TestTxnCrossProcessSetSynthetics(t *testing.T) { - // We'll always set SyntheticsHeader, since we're not really testing the full - // behaviour of IsSynthetics() here. - txp := &TxnCrossProcess{ - Type: 0, - Synthetics: &cat.SyntheticsHeader{}, - } - - txp.SetSynthetics(false) - if txp.IsSynthetics() != false { - t.Error("Synthetics is not false after being set to false from false") - } - - txp.SetSynthetics(true) - if txp.IsSynthetics() != true { - t.Error("Synthetics is not true after being set to true from false") - } - - txp.SetSynthetics(true) - if txp.IsSynthetics() != true { - t.Error("Synthetics is not true after being set to true from true") - } - - txp.SetSynthetics(false) - if txp.IsSynthetics() != false { - t.Error("Synthetics is not false after being set to false from true") - } -} - -func TestTxnCrossProcessParseAppData(t *testing.T) { - for _, tc := range []struct { - name string - encodingKey string - input string - expectedAppData *cat.AppDataHeader - expectedError bool - }{ - { - name: "empty string", - encodingKey: "foo", - input: "", - expectedAppData: nil, - expectedError: false, - }, - { - name: "invalidly encoded string", - encodingKey: "foo", - input: "xxx", - expectedAppData: nil, - expectedError: true, - }, - { - name: "invalid JSON", - encodingKey: "foo", - input: mustObfuscate("xxx", "foo"), - expectedAppData: nil, - expectedError: true, - }, - { - name: "invalid encoding key", - encodingKey: "foo", - input: mustObfuscate(`["xp","txn",1,2,3,"guid",false]`, "bar"), - expectedAppData: nil, - expectedError: true, - }, - { - name: "success", - encodingKey: "foo", - input: mustObfuscate(`["xp","txn",1,2,3,"guid",false]`, "foo"), - expectedAppData: &cat.AppDataHeader{ - CrossProcessID: "xp", - TransactionName: "txn", - QueueTimeInSeconds: 1, - ResponseTimeInSeconds: 2, - ContentLength: 3, - TransactionGUID: "guid", - }, - expectedError: false, - }, - } { - txp := &TxnCrossProcess{ - Enabled: true, - EncodingKey: []byte(tc.encodingKey), - } - - actualAppData, actualErr := txp.ParseAppData(tc.input) - - if tc.expectedError && actualErr == nil { - t.Errorf("%s: expected an error, but didn't get one", tc.name) - } else if tc.expectedError == false && actualErr != nil { - t.Errorf("%s: expected no error, but got %v", tc.name, actualErr) - } - - if !reflect.DeepEqual(actualAppData, tc.expectedAppData) { - t.Errorf("%s: app data mismatched: expected=%v; got=%v", tc.name, tc.expectedAppData, actualAppData) - } - } -} - -func TestTxnCrossProcessCreateAppData(t *testing.T) { - for _, tc := range []struct { - name string - enabled bool - crossProcessID string - encodingKey string - txnName string - queueTime time.Duration - responseTime time.Duration - contentLength int64 - guid string - expectedAppData string - expectedError bool - }{ - { - name: "cat disabled", - enabled: false, - crossProcessID: "1#1", - encodingKey: "foo", - txnName: "txn", - queueTime: 1 * time.Second, - responseTime: 2 * time.Second, - contentLength: 4096, - guid: "", - expectedAppData: "", - expectedError: false, - }, - { - name: "invalid encoding key", - enabled: true, - crossProcessID: "1#1", - encodingKey: "", - txnName: "txn", - queueTime: 1 * time.Second, - responseTime: 2 * time.Second, - contentLength: 4096, - guid: "", - expectedAppData: "", - expectedError: true, - }, - { - name: "success", - enabled: true, - crossProcessID: "1#1", - encodingKey: "foo", - txnName: "txn", - queueTime: 1 * time.Second, - responseTime: 2 * time.Second, - contentLength: 4096, - guid: "guid", - expectedAppData: mustObfuscate(`["1#1","txn",1,2,4096,"guid",false]`, "foo"), - expectedError: false, - }, - } { - txp := &TxnCrossProcess{ - Enabled: tc.enabled, - EncodingKey: []byte(tc.encodingKey), - CrossProcessID: []byte(tc.crossProcessID), - GUID: tc.guid, - } - - actualAppData, actualErr := txp.CreateAppData(tc.txnName, tc.queueTime, tc.responseTime, tc.contentLength) - - if tc.expectedError && actualErr == nil { - t.Errorf("%s: expected an error, but didn't get one", tc.name) - } else if tc.expectedError == false && actualErr != nil { - t.Errorf("%s: expected no error, but got %v", tc.name, actualErr) - } - - if !reflect.DeepEqual(actualAppData, tc.expectedAppData) { - t.Errorf("%s: app data mismatched: expected=%v; got=%v", tc.name, tc.expectedAppData, actualAppData) - } - } -} - -func TestTxnCrossProcessHandleInboundRequestHeaders(t *testing.T) { - for _, tc := range []struct { - name string - enabled bool - reply *ConnectReply - metadata CrossProcessMetadata - expectedError bool - }{ - { - name: "disabled, invalid encoding key, invalid synthetics", - enabled: false, - reply: &ConnectReply{ - EncodingKey: "", - }, - metadata: CrossProcessMetadata{ - Synthetics: "foo", - }, - expectedError: true, - }, - { - name: "disabled, valid encoding key, invalid synthetics", - enabled: false, - reply: replyAccountOne, - metadata: CrossProcessMetadata{ - Synthetics: "foo", - }, - expectedError: true, - }, - { - name: "disabled, valid encoding key, valid synthetics", - enabled: false, - reply: replyAccountOne, - metadata: CrossProcessMetadata{ - Synthetics: mustObfuscate(`[1,1,"resource","job","monitor"]`, "foo"), - }, - expectedError: false, - }, - { - name: "enabled, invalid encoding key, valid input", - enabled: true, - reply: &ConnectReply{ - EncodingKey: "", - }, - metadata: CrossProcessMetadata{ - ID: mustObfuscate(`1#1`, "foo"), - TxnData: mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"), - }, - expectedError: true, - }, - { - name: "enabled, valid encoding key, invalid id", - enabled: true, - reply: replyAccountOne, - metadata: CrossProcessMetadata{ - ID: mustObfuscate(`1`, "foo"), - TxnData: mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"), - }, - expectedError: true, - }, - { - name: "enabled, valid encoding key, invalid txndata", - enabled: true, - reply: replyAccountOne, - metadata: CrossProcessMetadata{ - ID: mustObfuscate(`1#1`, "foo"), - TxnData: mustObfuscate(`["00000000",invalid,"00000000","b95be233"]`, "foo"), - }, - expectedError: true, - }, - { - name: "enabled, valid encoding key, valid input", - enabled: true, - reply: replyAccountOne, - metadata: CrossProcessMetadata{ - ID: mustObfuscate(`1#1`, "foo"), - TxnData: mustObfuscate(`["00000000",false,"00000000","b95be233"]`, "foo"), - }, - expectedError: false, - }, - } { - txp := &TxnCrossProcess{Enabled: tc.enabled} - txp.Init(tc.enabled, false, tc.reply) - - err := txp.handleInboundRequestHeaders(tc.metadata) - if tc.expectedError && err == nil { - t.Errorf("%s: expected error, but didn't get one", tc.name) - } else if tc.expectedError == false && err != nil { - t.Errorf("%s: expected no error, but got %v", tc.name, err) - } - } -} diff --git a/internal/txn_events.go b/internal/txn_events.go deleted file mode 100644 index cda3905b8..000000000 --- a/internal/txn_events.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "sort" - "strings" - "time" -) - -// DatastoreExternalTotals contains overview of external and datastore calls -// made during a transaction. -type DatastoreExternalTotals struct { - externalCallCount uint64 - externalDuration time.Duration - datastoreCallCount uint64 - datastoreDuration time.Duration -} - -// WriteJSON prepares JSON in the format expected by the collector. -func (e *TxnEvent) WriteJSON(buf *bytes.Buffer) { - w := jsonFieldsWriter{buf: buf} - buf.WriteByte('[') - buf.WriteByte('{') - w.stringField("type", "Transaction") - w.stringField("name", e.FinalName) - w.floatField("timestamp", timeToFloatSeconds(e.Start)) - if ApdexNone != e.Zone { - w.stringField("nr.apdexPerfZone", e.Zone.label()) - } - - w.boolField("error", e.HasError) - - sharedTransactionIntrinsics(e, &w) - - // totalTime gets put into transaction events but not error events: - // https://source.datanerd.us/agents/agent-specs/blob/master/Total-Time-Async.md#attributes - w.floatField("totalTime", e.TotalTime.Seconds()) - - // Write better CAT intrinsics if enabled - sharedBetterCATIntrinsics(e, &w) - - if e.BetterCAT.Enabled { - if p := e.BetterCAT.Inbound; nil != p { - if "" != p.TransactionID { - w.stringField("parentId", p.TransactionID) - } - - if "" != p.ID { - w.stringField("parentSpanId", p.ID) - } - } - } - - // Write old CAT intrinsics if enabled - oldCATIntrinsics(e, &w) - - buf.WriteByte('}') - buf.WriteByte(',') - userAttributesJSON(e.Attrs, buf, destTxnEvent, nil) - buf.WriteByte(',') - agentAttributesJSON(e.Attrs, buf, destTxnEvent) - buf.WriteByte(']') -} - -// oldCATIntrinsics reports old CAT intrinsics for Transaction -// if CrossProcess.Used() is true -func oldCATIntrinsics(e *TxnEvent, w *jsonFieldsWriter) { - if !e.CrossProcess.Used() { - return - } - - if e.CrossProcess.ClientID != "" { - w.stringField("client_cross_process_id", e.CrossProcess.ClientID) - } - if e.CrossProcess.TripID != "" { - w.stringField("nr.tripId", e.CrossProcess.TripID) - } - if e.CrossProcess.PathHash != "" { - w.stringField("nr.pathHash", e.CrossProcess.PathHash) - } - if e.CrossProcess.ReferringPathHash != "" { - w.stringField("nr.referringPathHash", e.CrossProcess.ReferringPathHash) - } - if e.CrossProcess.GUID != "" { - w.stringField("nr.guid", e.CrossProcess.GUID) - } - if e.CrossProcess.ReferringTxnGUID != "" { - w.stringField("nr.referringTransactionGuid", e.CrossProcess.ReferringTxnGUID) - } - if len(e.CrossProcess.AlternatePathHashes) > 0 { - hashes := make([]string, 0, len(e.CrossProcess.AlternatePathHashes)) - for hash := range e.CrossProcess.AlternatePathHashes { - hashes = append(hashes, hash) - } - sort.Strings(hashes) - w.stringField("nr.alternatePathHashes", strings.Join(hashes, ",")) - } -} - -// sharedTransactionIntrinsics reports intrinsics that are shared -// by Transaction and TransactionError -func sharedTransactionIntrinsics(e *TxnEvent, w *jsonFieldsWriter) { - w.floatField("duration", e.Duration.Seconds()) - if e.Queuing > 0 { - w.floatField("queueDuration", e.Queuing.Seconds()) - } - if e.externalCallCount > 0 { - w.intField("externalCallCount", int64(e.externalCallCount)) - w.floatField("externalDuration", e.externalDuration.Seconds()) - } - if e.datastoreCallCount > 0 { - // Note that "database" is used for the keys here instead of - // "datastore" for historical reasons. - w.intField("databaseCallCount", int64(e.datastoreCallCount)) - w.floatField("databaseDuration", e.datastoreDuration.Seconds()) - } - - if e.CrossProcess.IsSynthetics() { - w.stringField("nr.syntheticsResourceId", e.CrossProcess.Synthetics.ResourceID) - w.stringField("nr.syntheticsJobId", e.CrossProcess.Synthetics.JobID) - w.stringField("nr.syntheticsMonitorId", e.CrossProcess.Synthetics.MonitorID) - } -} - -// sharedBetterCATIntrinsics reports intrinsics that are shared -// by Transaction, TransactionError, and Slow SQL -func sharedBetterCATIntrinsics(e *TxnEvent, w *jsonFieldsWriter) { - if e.BetterCAT.Enabled { - if p := e.BetterCAT.Inbound; nil != p { - w.stringField("parent.type", p.Type) - w.stringField("parent.app", p.App) - w.stringField("parent.account", p.Account) - w.stringField("parent.transportType", p.TransportType) - w.floatField("parent.transportDuration", p.TransportDuration.Seconds()) - } - - w.stringField("guid", e.BetterCAT.ID) - w.stringField("traceId", e.BetterCAT.TraceID()) - w.writerField("priority", e.BetterCAT.Priority) - w.boolField("sampled", e.BetterCAT.Sampled) - } -} - -// MarshalJSON is used for testing. -func (e *TxnEvent) MarshalJSON() ([]byte, error) { - buf := bytes.NewBuffer(make([]byte, 0, 256)) - - e.WriteJSON(buf) - - return buf.Bytes(), nil -} - -type txnEvents struct { - *analyticsEvents -} - -func newTxnEvents(max int) *txnEvents { - return &txnEvents{ - analyticsEvents: newAnalyticsEvents(max), - } -} - -func (events *txnEvents) AddTxnEvent(e *TxnEvent, priority Priority) { - // Synthetics events always get priority: normal event priorities are in the - // range [0.0,1.99999], so adding 2 means that a Synthetics event will always - // win. - if e.CrossProcess.IsSynthetics() { - priority += 2.0 - } - events.addEvent(analyticsEvent{priority: priority, jsonWriter: e}) -} - -func (events *txnEvents) MergeIntoHarvest(h *Harvest) { - h.TxnEvents.mergeFailed(events.analyticsEvents) -} - -func (events *txnEvents) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { - return events.CollectorJSON(agentRunID) -} - -func (events *txnEvents) EndpointMethod() string { - return cmdTxnEvents -} - -func (events *txnEvents) payloads(limit int) []PayloadCreator { - if events.NumSaved() < float64(limit) { - return []PayloadCreator{events} - } - e1, e2 := events.split() - return []PayloadCreator{ - &txnEvents{analyticsEvents: e1}, - &txnEvents{analyticsEvents: e2}, - } -} diff --git a/internal/txn_events_test.go b/internal/txn_events_test.go deleted file mode 100644 index 268937341..000000000 --- a/internal/txn_events_test.go +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "encoding/json" - "testing" - "time" - - "github.com/newrelic/go-agent/internal/cat" -) - -func testTxnEventJSON(t testing.TB, e *TxnEvent, expect string) { - // Type assertion to support early Go versions. - if h, ok := t.(interface { - Helper() - }); ok { - h.Helper() - } - js, err := json.Marshal(e) - if nil != err { - t.Error(err) - return - } - expect = CompactJSONString(expect) - if string(js) != expect { - t.Errorf("\nexpect=%s\nactual=%s\n", expect, string(js)) - } -} - -var ( - sampleTxnEvent = TxnEvent{ - FinalName: "myName", - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - Start: timeFromUnixMilliseconds(1488393111000), - Duration: 2 * time.Second, - TotalTime: 3 * time.Second, - Zone: ApdexNone, - Attrs: nil, - } - - sampleTxnEventWithOldCAT = TxnEvent{ - FinalName: "myOldName", - BetterCAT: BetterCAT{ - Enabled: false, - }, - Start: timeFromUnixMilliseconds(1488393111000), - Duration: 2 * time.Second, - TotalTime: 3 * time.Second, - Zone: ApdexNone, - Attrs: nil, - } - - sampleTxnEventWithError = TxnEvent{ - FinalName: "myName", - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - Start: timeFromUnixMilliseconds(1488393111000), - Duration: 2 * time.Second, - TotalTime: 3 * time.Second, - Zone: ApdexNone, - Attrs: nil, - HasError: true, - } -) - -func TestTxnEventMarshal(t *testing.T) { - e := sampleTxnEvent - testTxnEventJSON(t, &e, `[ - { - "type":"Transaction", - "name":"myName", - "timestamp":1.488393111e+09, - "error":false, - "duration":2, - "totalTime":3, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - }, - {}, - {}]`) -} - -func TestTxnEventMarshalWithApdex(t *testing.T) { - e := sampleTxnEvent - e.Zone = ApdexFailing - testTxnEventJSON(t, &e, `[ - { - "type":"Transaction", - "name":"myName", - "timestamp":1.488393111e+09, - "nr.apdexPerfZone":"F", - "error":false, - "duration":2, - "totalTime":3, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - }, - {}, - {}]`) -} - -func TestTxnEventMarshalWithDatastoreExternal(t *testing.T) { - e := sampleTxnEvent - e.DatastoreExternalTotals = DatastoreExternalTotals{ - externalCallCount: 22, - externalDuration: 1122334 * time.Millisecond, - datastoreCallCount: 33, - datastoreDuration: 5566778 * time.Millisecond, - } - testTxnEventJSON(t, &e, `[ - { - "type":"Transaction", - "name":"myName", - "timestamp":1.488393111e+09, - "error":false, - "duration":2, - "externalCallCount":22, - "externalDuration":1122.334, - "databaseCallCount":33, - "databaseDuration":5566.778, - "totalTime":3, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - }, - {}, - {}]`) -} - -func TestTxnEventMarshalWithInboundCaller(t *testing.T) { - e := sampleTxnEvent - e.BetterCAT.Inbound = &Payload{ - payloadCaller: payloadCaller{ - TransportType: "HTTP", - Type: "Browser", - App: "caller-app", - Account: "caller-account", - }, - ID: "caller-id", - TransactionID: "caller-parent-id", - TracedID: "trip-id", - TransportDuration: 2 * time.Second, - } - testTxnEventJSON(t, &e, `[ - { - "type":"Transaction", - "name":"myName", - "timestamp":1.488393111e+09, - "error":false, - "duration":2, - "totalTime":3, - "parent.type": "Browser", - "parent.app": "caller-app", - "parent.account": "caller-account", - "parent.transportType": "HTTP", - "parent.transportDuration": 2, - "guid":"txn-id", - "traceId":"trip-id", - "priority":0.500000, - "sampled":false, - "parentId": "caller-parent-id", - "parentSpanId": "caller-id" - }, - {}, - {}]`) -} - -func TestTxnEventMarshalWithInboundCallerOldCAT(t *testing.T) { - e := sampleTxnEventWithOldCAT - e.BetterCAT.Inbound = &Payload{ - payloadCaller: payloadCaller{ - TransportType: "HTTP", - Type: "Browser", - App: "caller-app", - Account: "caller-account", - }, - ID: "caller-id", - TransactionID: "caller-parent-id", - TracedID: "trip-id", - TransportDuration: 2 * time.Second, - } - testTxnEventJSON(t, &e, `[ - { - "type":"Transaction", - "name":"myOldName", - "timestamp":1.488393111e+09, - "error":false, - "duration":2, - "totalTime":3 - }, - {}, - {}]`) -} - -func TestTxnEventMarshalWithAttributes(t *testing.T) { - aci := sampleAttributeConfigInput - aci.TransactionEvents.Exclude = append(aci.TransactionEvents.Exclude, "zap") - aci.TransactionEvents.Exclude = append(aci.TransactionEvents.Exclude, AttributeHostDisplayName.name()) - cfg := CreateAttributeConfig(aci, true) - attr := NewAttributes(cfg) - attr.Agent.Add(AttributeHostDisplayName, "exclude me", nil) - attr.Agent.Add(attributeRequestMethod, "GET", nil) - AddUserAttribute(attr, "zap", 123, DestAll) - AddUserAttribute(attr, "zip", 456, DestAll) - e := sampleTxnEvent - e.Attrs = attr - testTxnEventJSON(t, &e, `[ - { - "type":"Transaction", - "name":"myName", - "timestamp":1.488393111e+09, - "error":false, - "duration":2, - "totalTime":3, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - }, - { - "zip":456 - }, - { - "request.method":"GET" - }]`) -} - -func TestTxnEventsPayloadsEmpty(t *testing.T) { - events := newTxnEvents(10) - ps := events.payloads(5) - if len(ps) != 1 { - t.Error(ps) - } - if data, err := ps[0].Data("agentRunID", time.Now()); data != nil || err != nil { - t.Error(data, err) - } -} - -func TestTxnEventsPayloadsUnderLimit(t *testing.T) { - events := newTxnEvents(10) - for i := 0; i < 4; i++ { - events.AddTxnEvent(&TxnEvent{}, Priority(float32(i)/10.0)) - } - ps := events.payloads(5) - if len(ps) != 1 { - t.Error(ps) - } - if data, err := ps[0].Data("agentRunID", time.Now()); data == nil || err != nil { - t.Error(data, err) - } -} - -func TestTxnEventsPayloadsOverLimit(t *testing.T) { - events := newTxnEvents(10) - for i := 0; i < 6; i++ { - events.AddTxnEvent(&TxnEvent{}, Priority(float32(i)/10.0)) - } - ps := events.payloads(5) - if len(ps) != 2 { - t.Error(ps) - } - if data, err := ps[0].Data("agentRunID", time.Now()); data == nil || err != nil { - t.Error(data, err) - } - if data, err := ps[1].Data("agentRunID", time.Now()); data == nil || err != nil { - t.Error(data, err) - } -} - -func TestTxnEventsSynthetics(t *testing.T) { - events := newTxnEvents(1) - - regular := &TxnEvent{ - FinalName: "Regular", - Start: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), - Duration: 2 * time.Second, - Zone: ApdexNone, - Attrs: nil, - } - - synthetics := &TxnEvent{ - FinalName: "Synthetics", - Start: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), - Duration: 2 * time.Second, - Zone: ApdexNone, - Attrs: nil, - CrossProcess: TxnCrossProcess{ - Type: txnCrossProcessSynthetics, - Synthetics: &cat.SyntheticsHeader{ - ResourceID: "resource", - JobID: "job", - MonitorID: "monitor", - }, - }, - } - - events.AddTxnEvent(regular, 1.99999) - - // Check that the event was saved. - if saved := events.analyticsEvents.events[0].jsonWriter; saved != regular { - t.Errorf("unexpected saved event: expected=%v; got=%v", regular, saved) - } - - // The priority sampling algorithm is implemented using isLowerPriority(). In - // the case of an event pool with a single event, an incoming event with the - // same priority would kick out the event already in the pool. To really test - // whether synthetics are given highest deference, add a synthetics event - // with a really low priority and affirm it kicks out the event already in the - // pool. - events.AddTxnEvent(synthetics, 0.0) - - // Check that the event was saved and its priority was appropriately augmented. - if saved := events.analyticsEvents.events[0].jsonWriter; saved != synthetics { - t.Errorf("unexpected saved event: expected=%v; got=%v", synthetics, saved) - } - - if priority := events.analyticsEvents.events[0].priority; priority != 2.0 { - t.Errorf("synthetics event has unexpected priority: %f", priority) - } -} - -func TestTxnEventMarshalWithError(t *testing.T) { - e := sampleTxnEventWithError - testTxnEventJSON(t, &e, `[ - { - "type":"Transaction", - "name":"myName", - "timestamp":1.488393111e+09, - "error":true, - "duration":2, - "totalTime":3, - "guid":"txn-id", - "traceId":"txn-id", - "priority":0.500000, - "sampled":false - }, - {}, - {}]`) -} diff --git a/internal/txn_trace.go b/internal/txn_trace.go deleted file mode 100644 index 7e155089b..000000000 --- a/internal/txn_trace.go +++ /dev/null @@ -1,453 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "container/heap" - "sort" - "time" - - "github.com/newrelic/go-agent/internal/jsonx" -) - -// See https://source.datanerd.us/agents/agent-specs/blob/master/Transaction-Trace-LEGACY.md - -type traceNodeHeap []traceNode - -type traceNodeParams struct { - attributes map[SpanAttribute]jsonWriter - StackTrace StackTrace - TransactionGUID string - exclusiveDurationMillis *float64 -} - -type traceNode struct { - start segmentTime - stop segmentTime - threadID uint64 - duration time.Duration - traceNodeParams - name string -} - -func (h traceNodeHeap) Len() int { return len(h) } -func (h traceNodeHeap) Less(i, j int) bool { return h[i].duration < h[j].duration } -func (h traceNodeHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } - -// Push and Pop are unused: only heap.Init and heap.Fix are used. -func (h traceNodeHeap) Push(x interface{}) {} -func (h traceNodeHeap) Pop() interface{} { return nil } - -// TxnTrace contains the work in progress transaction trace. -type TxnTrace struct { - Enabled bool - SegmentThreshold time.Duration - StackTraceThreshold time.Duration - nodes traceNodeHeap - maxNodes int -} - -// getMaxNodes allows the maximum number of nodes to be overwritten for unit -// tests. -func (trace *TxnTrace) getMaxNodes() int { - if 0 != trace.maxNodes { - return trace.maxNodes - } - return maxTxnTraceNodes -} - -// considerNode exists to prevent unnecessary calls to witnessNode: constructing -// the metric name and params map requires allocations. -func (trace *TxnTrace) considerNode(end segmentEnd) bool { - return trace.Enabled && (end.duration >= trace.SegmentThreshold) -} - -func (trace *TxnTrace) witnessNode(end segmentEnd, name string, attrs spanAttributeMap, externalGUID string) { - node := traceNode{ - start: end.start, - stop: end.stop, - duration: end.duration, - threadID: end.threadID, - name: name, - } - node.attributes = attrs - node.TransactionGUID = externalGUID - if !trace.considerNode(end) { - return - } - if trace.nodes == nil { - trace.nodes = make(traceNodeHeap, 0, startingTxnTraceNodes) - } - if end.exclusive >= trace.StackTraceThreshold { - node.StackTrace = GetStackTrace() - } - if max := trace.getMaxNodes(); len(trace.nodes) < max { - trace.nodes = append(trace.nodes, node) - if len(trace.nodes) == max { - heap.Init(trace.nodes) - } - return - } - - if node.duration <= trace.nodes[0].duration { - return - } - trace.nodes[0] = node - heap.Fix(trace.nodes, 0) -} - -// HarvestTrace contains a finished transaction trace ready for serialization to -// the collector. -type HarvestTrace struct { - TxnEvent - Trace TxnTrace -} - -type nodeDetails struct { - name string - relativeStart time.Duration - relativeStop time.Duration - traceNodeParams -} - -func printNodeStart(buf *bytes.Buffer, n nodeDetails) { - // time.Seconds() is intentionally not used here. Millisecond - // precision is enough. - relativeStartMillis := n.relativeStart.Nanoseconds() / (1000 * 1000) - relativeStopMillis := n.relativeStop.Nanoseconds() / (1000 * 1000) - - buf.WriteByte('[') - jsonx.AppendInt(buf, relativeStartMillis) - buf.WriteByte(',') - jsonx.AppendInt(buf, relativeStopMillis) - buf.WriteByte(',') - jsonx.AppendString(buf, n.name) - buf.WriteByte(',') - - w := jsonFieldsWriter{buf: buf} - buf.WriteByte('{') - if nil != n.StackTrace { - w.writerField("backtrace", n.StackTrace) - } - if nil != n.exclusiveDurationMillis { - w.floatField("exclusive_duration_millis", *n.exclusiveDurationMillis) - } - if "" != n.TransactionGUID { - w.stringField("transaction_guid", n.TransactionGUID) - } - for k, v := range n.attributes { - w.writerField(k.String(), v) - } - buf.WriteByte('}') - - buf.WriteByte(',') - buf.WriteByte('[') -} - -func printChildren(buf *bytes.Buffer, traceStart time.Time, nodes sortedTraceNodes, next int, stop *segmentStamp, threadID uint64) int { - firstChild := true - for { - if next >= len(nodes) { - // No more children to print. - break - } - if nodes[next].threadID != threadID { - // The next node is not of the same thread. Due to the - // node sorting, all nodes of the same thread should be - // together. - break - } - if stop != nil && nodes[next].start.Stamp >= *stop { - // Make sure this node is a child of the parent that is - // being printed. - break - } - if firstChild { - firstChild = false - } else { - buf.WriteByte(',') - } - printNodeStart(buf, nodeDetails{ - name: nodes[next].name, - relativeStart: nodes[next].start.Time.Sub(traceStart), - relativeStop: nodes[next].stop.Time.Sub(traceStart), - traceNodeParams: nodes[next].traceNodeParams, - }) - next = printChildren(buf, traceStart, nodes, next+1, &nodes[next].stop.Stamp, threadID) - buf.WriteString("]]") - - } - return next -} - -type sortedTraceNodes []*traceNode - -func (s sortedTraceNodes) Len() int { return len(s) } -func (s sortedTraceNodes) Less(i, j int) bool { - // threadID is the first sort key and start.Stamp is the second key. - if s[i].threadID == s[j].threadID { - return s[i].start.Stamp < s[j].start.Stamp - } - return s[i].threadID < s[j].threadID -} -func (s sortedTraceNodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// MarshalJSON is used for testing. -// -// TODO: Eliminate this entirely by using harvestTraces.Data(). -func (trace *HarvestTrace) MarshalJSON() ([]byte, error) { - buf := bytes.NewBuffer(make([]byte, 0, 100+100*trace.Trace.nodes.Len())) - - trace.writeJSON(buf) - - return buf.Bytes(), nil -} - -func (trace *HarvestTrace) writeJSON(buf *bytes.Buffer) { - nodes := make(sortedTraceNodes, len(trace.Trace.nodes)) - for i := 0; i < len(nodes); i++ { - nodes[i] = &trace.Trace.nodes[i] - } - sort.Sort(nodes) - - buf.WriteByte('[') // begin trace - - jsonx.AppendInt(buf, trace.Start.UnixNano()/1000) - buf.WriteByte(',') - jsonx.AppendFloat(buf, trace.Duration.Seconds()*1000.0) - buf.WriteByte(',') - jsonx.AppendString(buf, trace.FinalName) - buf.WriteByte(',') - if uri, _ := trace.Attrs.GetAgentValue(attributeRequestURI, destTxnTrace); "" != uri { - jsonx.AppendString(buf, uri) - } else { - buf.WriteString("null") - } - buf.WriteByte(',') - - buf.WriteByte('[') // begin trace data - - // If the trace string pool is used, insert another array here. - - jsonx.AppendFloat(buf, 0.0) // unused timestamp - buf.WriteByte(',') // - buf.WriteString("{}") // unused: formerly request parameters - buf.WriteByte(',') // - buf.WriteString("{}") // unused: formerly custom parameters - buf.WriteByte(',') // - - printNodeStart(buf, nodeDetails{ // begin outer root - name: "ROOT", - relativeStart: 0, - relativeStop: trace.Duration, - }) - - // exclusive_duration_millis field is added to fix the transaction trace - // summary tab. If exclusive_duration_millis is not provided, the UIs - // will calculate exclusive time, which doesn't work for this root node - // since all async goroutines are children of this root. - exclusiveDurationMillis := trace.Duration.Seconds() * 1000.0 - details := nodeDetails{ // begin inner root - name: trace.FinalName, - relativeStart: 0, - relativeStop: trace.Duration, - } - details.exclusiveDurationMillis = &exclusiveDurationMillis - printNodeStart(buf, details) - - for next := 0; next < len(nodes); { - if next > 0 { - buf.WriteByte(',') - } - // We put each thread's nodes into the root node instead of the - // node that spawned the thread. This approach is simple and - // works when the segment which spawned a thread has been pruned - // from the trace. Each call to printChildren prints one - // thread. - next = printChildren(buf, trace.Start, nodes, next, nil, nodes[next].threadID) - } - - buf.WriteString("]]") // end outer root - buf.WriteString("]]") // end inner root - - buf.WriteByte(',') - buf.WriteByte('{') - buf.WriteString(`"agentAttributes":`) - agentAttributesJSON(trace.Attrs, buf, destTxnTrace) - buf.WriteByte(',') - buf.WriteString(`"userAttributes":`) - userAttributesJSON(trace.Attrs, buf, destTxnTrace, nil) - buf.WriteByte(',') - buf.WriteString(`"intrinsics":`) - intrinsicsJSON(&trace.TxnEvent, buf) - buf.WriteByte('}') - - // If the trace string pool is used, end another array here. - - buf.WriteByte(']') // end trace data - - // catGUID - buf.WriteByte(',') - if trace.CrossProcess.Used() && trace.CrossProcess.GUID != "" { - jsonx.AppendString(buf, trace.CrossProcess.GUID) - } else if trace.BetterCAT.Enabled { - jsonx.AppendString(buf, trace.BetterCAT.TraceID()) - } else { - buf.WriteString(`""`) - } - buf.WriteByte(',') // - buf.WriteString(`null`) // reserved for future use - buf.WriteByte(',') // - buf.WriteString(`false`) // ForcePersist is not yet supported - buf.WriteByte(',') // - buf.WriteString(`null`) // X-Ray sessions not supported - buf.WriteByte(',') // - - // Synthetics are supported: - if trace.CrossProcess.IsSynthetics() { - jsonx.AppendString(buf, trace.CrossProcess.Synthetics.ResourceID) - } else { - buf.WriteString(`""`) - } - - buf.WriteByte(']') // end trace -} - -type txnTraceHeap []*HarvestTrace - -func (h *txnTraceHeap) isEmpty() bool { - return 0 == len(*h) -} - -func newTxnTraceHeap(max int) *txnTraceHeap { - h := make(txnTraceHeap, 0, max) - heap.Init(&h) - return &h -} - -// Implement sort.Interface. -func (h txnTraceHeap) Len() int { return len(h) } -func (h txnTraceHeap) Less(i, j int) bool { return h[i].Duration < h[j].Duration } -func (h txnTraceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } - -// Implement heap.Interface. -func (h *txnTraceHeap) Push(x interface{}) { *h = append(*h, x.(*HarvestTrace)) } - -func (h *txnTraceHeap) Pop() interface{} { - old := *h - n := len(old) - x := old[n-1] - *h = old[0 : n-1] - return x -} - -func (h *txnTraceHeap) isKeeper(t *HarvestTrace) bool { - if len(*h) < cap(*h) { - return true - } - return t.Duration >= (*h)[0].Duration -} - -func (h *txnTraceHeap) addTxnTrace(t *HarvestTrace) { - if len(*h) < cap(*h) { - heap.Push(h, t) - return - } - - if t.Duration <= (*h)[0].Duration { - return - } - heap.Pop(h) - heap.Push(h, t) -} - -type harvestTraces struct { - regular *txnTraceHeap - synthetics *txnTraceHeap -} - -func newHarvestTraces() *harvestTraces { - return &harvestTraces{ - regular: newTxnTraceHeap(maxRegularTraces), - synthetics: newTxnTraceHeap(maxSyntheticsTraces), - } -} - -func (traces *harvestTraces) Len() int { - return traces.regular.Len() + traces.synthetics.Len() -} - -func (traces *harvestTraces) Witness(trace HarvestTrace) { - traceHeap := traces.regular - if trace.CrossProcess.IsSynthetics() { - traceHeap = traces.synthetics - } - - if traceHeap.isKeeper(&trace) { - cpy := new(HarvestTrace) - *cpy = trace - traceHeap.addTxnTrace(cpy) - } -} - -func (traces *harvestTraces) Data(agentRunID string, harvestStart time.Time) ([]byte, error) { - if traces.Len() == 0 { - return nil, nil - } - - // This estimate is used to guess the size of the buffer. No worries if - // the estimate is small since the buffer will be lengthened as - // necessary. This is just about minimizing reallocations. - estimate := 512 - for _, t := range *traces.regular { - estimate += 100 * t.Trace.nodes.Len() - } - for _, t := range *traces.synthetics { - estimate += 100 * t.Trace.nodes.Len() - } - - buf := bytes.NewBuffer(make([]byte, 0, estimate)) - buf.WriteByte('[') - jsonx.AppendString(buf, agentRunID) - buf.WriteByte(',') - buf.WriteByte('[') - - // use a function to add traces to the buffer to avoid duplicating comma - // logic in both loops - firstTrace := true - addTrace := func(trace *HarvestTrace) { - if firstTrace { - firstTrace = false - } else { - buf.WriteByte(',') - } - trace.writeJSON(buf) - } - - for _, trace := range *traces.regular { - addTrace(trace) - } - for _, trace := range *traces.synthetics { - addTrace(trace) - } - buf.WriteByte(']') - buf.WriteByte(']') - - return buf.Bytes(), nil -} - -func (traces *harvestTraces) slice() []*HarvestTrace { - out := make([]*HarvestTrace, 0, traces.Len()) - out = append(out, (*traces.regular)...) - out = append(out, (*traces.synthetics)...) - - return out -} - -func (traces *harvestTraces) MergeIntoHarvest(h *Harvest) {} - -func (traces *harvestTraces) EndpointMethod() string { - return cmdTxnTraces -} diff --git a/internal/txn_trace_test.go b/internal/txn_trace_test.go deleted file mode 100644 index f8069e1d5..000000000 --- a/internal/txn_trace_test.go +++ /dev/null @@ -1,1286 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "net/http" - "strconv" - "testing" - "time" - - "github.com/newrelic/go-agent/internal/cat" - "github.com/newrelic/go-agent/internal/logger" -) - -func TestTxnTrace(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - txndata.TxnTrace.Enabled = true - txndata.TxnTrace.StackTraceThreshold = 1 * time.Hour - txndata.TxnTrace.SegmentThreshold = 0 - - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - t2 := StartSegment(txndata, thread, start.Add(2*time.Second)) - qParams, err := vetQueryParameters(map[string]interface{}{"zip": 1}) - if nil != err { - t.Error("error creating query params", err) - } - EndDatastoreSegment(EndDatastoreParams{ - TxnData: txndata, - Thread: thread, - Start: t2, - Now: start.Add(3 * time.Second), - Product: "MySQL", - Operation: "SELECT", - Collection: "my_table", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - QueryParameters: qParams, - Database: "my_db", - Host: "db-server-1", - PortPathOrID: "3306", - }) - t3 := StartSegment(txndata, thread, start.Add(4*time.Second)) - EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t3, - Now: start.Add(5 * time.Second), - URL: parseURL("http://example.com/zip/zap?secret=shhh"), - Logger: logger.ShimLogger{}, - }) - EndBasicSegment(txndata, thread, t1, start.Add(6*time.Second), "t1") - t4 := StartSegment(txndata, thread, start.Add(7*time.Second)) - t5 := StartSegment(txndata, thread, start.Add(8*time.Second)) - t6 := StartSegment(txndata, thread, start.Add(9*time.Second)) - EndBasicSegment(txndata, thread, t6, start.Add(10*time.Second), "t6") - EndBasicSegment(txndata, thread, t5, start.Add(11*time.Second), "t5") - t7 := StartSegment(txndata, thread, start.Add(12*time.Second)) - EndDatastoreSegment(EndDatastoreParams{ - TxnData: txndata, - Thread: thread, - Start: t7, - Now: start.Add(13 * time.Second), - Product: "MySQL", - Operation: "SELECT", - // no collection - }) - t8 := StartSegment(txndata, thread, start.Add(14*time.Second)) - EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t8, - Now: start.Add(15 * time.Second), - URL: nil, - Logger: logger.ShimLogger{}, - }) - EndBasicSegment(txndata, thread, t4, start.Add(16*time.Second), "t4") - - t9 := StartSegment(txndata, thread, start.Add(17*time.Second)) - EndMessageSegment(EndMessageParams{ - TxnData: txndata, - Thread: thread, - Start: t9, - Now: start.Add(18 * time.Second), - Logger: nil, - DestinationName: "MyTopic", - Library: "Kafka", - DestinationType: "Topic", - }) - - acfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/url", nil) - AddUserAttribute(attr, "zap", 123, DestAll) - - ht := newHarvestTraces() - ht.regular.addTxnTrace(&HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 20 * time.Second, - TotalTime: 30 * time.Second, - FinalName: "WebTransaction/Go/hello", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - UserAttributes: map[string]interface{}{"zap": 123}, - AgentAttributes: map[string]interface{}{"request.uri": "/url"}, - Intrinsics: map[string]interface{}{ - "guid": "txn-id", - "traceId": "txn-id", - "priority": 0.500000, - "sampled": false, - "totalTime": 30, - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/hello", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 20000}, - Children: []WantTraceSegment{ - { - SegmentName: "Custom/t1", - RelativeStartMillis: 1000, - RelativeStopMillis: 6000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{ - { - SegmentName: "Datastore/statement/MySQL/my_table/SELECT", - RelativeStartMillis: 2000, - RelativeStopMillis: 3000, - Attributes: map[string]interface{}{ - "db.instance": "my_db", - "peer.hostname": "db-server-1", - "peer.address": "db-server-1:3306", - "db.statement": "INSERT INTO users (name, age) VALUES ($1, $2)", - "query_parameters": "map[zip:1]", - }, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "External/example.com/http", - RelativeStartMillis: 4000, - RelativeStopMillis: 5000, - Attributes: map[string]interface{}{ - "http.url": "http://example.com/zip/zap", - }, - Children: []WantTraceSegment{}, - }, - }, - }, - { - SegmentName: "Custom/t4", - RelativeStartMillis: 7000, - RelativeStopMillis: 16000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{ - { - SegmentName: "Custom/t5", - RelativeStartMillis: 8000, - RelativeStopMillis: 11000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{ - { - SegmentName: "Custom/t6", - RelativeStartMillis: 9000, - RelativeStopMillis: 10000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - }, - }, - { - SegmentName: "Datastore/operation/MySQL/SELECT", - RelativeStartMillis: 12000, - RelativeStopMillis: 13000, - Attributes: map[string]interface{}{ - "db.statement": "'SELECT' on 'unknown' using 'MySQL'", - }, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "External/unknown/http", - RelativeStartMillis: 14000, - RelativeStopMillis: 15000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - }, - }, - { - SegmentName: "MessageBroker/Kafka/Topic/Produce/Named/MyTopic", - RelativeStartMillis: 17000, - RelativeStopMillis: 18000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - }, - }}, - }, - }}) -} - -func TestTxnTraceNoNodes(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - txndata.TxnTrace.Enabled = true - txndata.TxnTrace.StackTraceThreshold = 1 * time.Hour - txndata.TxnTrace.SegmentThreshold = 0 - - ht := newHarvestTraces() - ht.regular.addTxnTrace(&HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 20 * time.Second, - TotalTime: 30 * time.Second, - FinalName: "WebTransaction/Go/hello", - Attrs: nil, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{ - "guid": "txn-id", - "traceId": "txn-id", - "priority": 0.500000, - "sampled": false, - "totalTime": 30, - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/hello", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 20000}, - Children: []WantTraceSegment{}, - }}, - }, - }}) -} - -func TestTxnTraceAsync(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{ - TraceIDGenerator: NewTraceIDGenerator(12345), - } - thread1 := &Thread{} - txndata.TxnTrace.Enabled = true - txndata.TxnTrace.StackTraceThreshold = 1 * time.Hour - txndata.TxnTrace.SegmentThreshold = 0 - txndata.BetterCAT.Sampled = true - txndata.SpanEventsEnabled = true - txndata.LazilyCalculateSampled = func() bool { return true } - - t1s1 := StartSegment(txndata, thread1, start.Add(1*time.Second)) - t1s2 := StartSegment(txndata, thread1, start.Add(2*time.Second)) - thread2 := NewThread(txndata) - t2s1 := StartSegment(txndata, thread2, start.Add(3*time.Second)) - EndBasicSegment(txndata, thread1, t1s2, start.Add(4*time.Second), "thread1.segment2") - EndBasicSegment(txndata, thread2, t2s1, start.Add(5*time.Second), "thread2.segment1") - thread3 := NewThread(txndata) - t3s1 := StartSegment(txndata, thread3, start.Add(6*time.Second)) - t3s2 := StartSegment(txndata, thread3, start.Add(7*time.Second)) - EndBasicSegment(txndata, thread1, t1s1, start.Add(8*time.Second), "thread1.segment1") - EndBasicSegment(txndata, thread3, t3s2, start.Add(9*time.Second), "thread3.segment2") - EndBasicSegment(txndata, thread3, t3s1, start.Add(10*time.Second), "thread3.segment1") - - if tt := thread1.TotalTime(); tt != 7*time.Second { - t.Error(tt) - } - if tt := thread2.TotalTime(); tt != 2*time.Second { - t.Error(tt) - } - if tt := thread3.TotalTime(); tt != 4*time.Second { - t.Error(tt) - } - - if len(txndata.spanEvents) != 5 { - t.Fatal(txndata.spanEvents) - } - for _, e := range txndata.spanEvents { - if e.GUID == "" || e.ParentID == "" { - t.Error(e.GUID, e.ParentID) - } - } - spanEventT1S2 := txndata.spanEvents[0] - spanEventT2S1 := txndata.spanEvents[1] - spanEventT1S1 := txndata.spanEvents[2] - spanEventT3S2 := txndata.spanEvents[3] - spanEventT3S1 := txndata.spanEvents[4] - - if txndata.rootSpanID == "" { - t.Error(txndata.rootSpanID) - } - if spanEventT1S1.ParentID != txndata.rootSpanID { - t.Error(spanEventT1S1.ParentID, txndata.rootSpanID) - } - if spanEventT1S2.ParentID != spanEventT1S1.GUID { - t.Error(spanEventT1S2.ParentID, spanEventT1S1.GUID) - } - if spanEventT2S1.ParentID != txndata.rootSpanID { - t.Error(spanEventT2S1.ParentID, txndata.rootSpanID) - } - if spanEventT3S1.ParentID != txndata.rootSpanID { - t.Error(spanEventT3S1.ParentID, txndata.rootSpanID) - } - if spanEventT3S2.ParentID != spanEventT3S1.GUID { - t.Error(spanEventT3S2.ParentID, spanEventT3S1.GUID) - } - - ht := newHarvestTraces() - ht.regular.addTxnTrace(&HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 20 * time.Second, - TotalTime: 30 * time.Second, - FinalName: "WebTransaction/Go/hello", - Attrs: nil, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{ - "totalTime": 30, - "guid": "txn-id", - "traceId": "txn-id", - "priority": 0.500000, - "sampled": false, - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/hello", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 20000}, - Children: []WantTraceSegment{ - { - SegmentName: "Custom/thread1.segment1", - RelativeStartMillis: 1000, - RelativeStopMillis: 8000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{ - { - SegmentName: "Custom/thread1.segment2", - RelativeStartMillis: 2000, - RelativeStopMillis: 4000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - }, - }, - { - SegmentName: "Custom/thread2.segment1", - RelativeStartMillis: 3000, - RelativeStopMillis: 5000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "Custom/thread3.segment1", - RelativeStartMillis: 6000, - RelativeStopMillis: 10000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{ - { - SegmentName: "Custom/thread3.segment2", - RelativeStartMillis: 7000, - RelativeStopMillis: 9000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - }, - }, - }, - }}, - }, - }}) -} - -func TestTxnTraceOldCAT(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - txndata.TxnTrace.Enabled = true - txndata.TxnTrace.StackTraceThreshold = 1 * time.Hour - txndata.TxnTrace.SegmentThreshold = 0 - - txndata.CrossProcess.Init(true, false, replyAccountOne) - txndata.CrossProcess.GUID = "0123456789" - appData, err := txndata.CrossProcess.CreateAppData("WebTransaction/Go/otherService", 2*time.Second, 3*time.Second, 123) - if nil != err { - t.Fatal(err) - } - resp := &http.Response{ - Header: AppDataToHTTPHeader(appData), - } - t3 := StartSegment(txndata, thread, start.Add(4*time.Second)) - EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t3, - Now: start.Add(5 * time.Second), - URL: parseURL("http://example.com/zip/zap?secret=shhh"), - Response: resp, - Logger: logger.ShimLogger{}, - }) - - acfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/url", nil) - AddUserAttribute(attr, "zap", 123, DestAll) - - ht := newHarvestTraces() - ht.regular.addTxnTrace(&HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 20 * time.Second, - TotalTime: 30 * time.Second, - FinalName: "WebTransaction/Go/hello", - Attrs: attr, - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - UserAttributes: map[string]interface{}{"zap": 123}, - AgentAttributes: map[string]interface{}{"request.uri": "/url"}, - Intrinsics: map[string]interface{}{"totalTime": 30}, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/hello", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 20000}, - Children: []WantTraceSegment{ - { - SegmentName: "ExternalTransaction/example.com/1#1/WebTransaction/Go/otherService", - RelativeStartMillis: 4000, - RelativeStopMillis: 5000, - Attributes: map[string]interface{}{ - "http.url": "http://example.com/zip/zap", - "transaction_guid": "0123456789", - }, - Children: []WantTraceSegment{}, - }, - }, - }}, - }, - }}) -} - -func TestTxnTraceExcludeURI(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - tr := &TxnData{} - tr.TxnTrace.Enabled = true - tr.TxnTrace.StackTraceThreshold = 1 * time.Hour - tr.TxnTrace.SegmentThreshold = 0 - - c := sampleAttributeConfigInput - c.TransactionTracer.Exclude = []string{"request.uri"} - acfg := CreateAttributeConfig(c, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/url", nil) - - ht := newHarvestTraces() - ht.regular.addTxnTrace(&HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 20 * time.Second, - FinalName: "WebTransaction/Go/hello", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - }, - Trace: tr.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{ - "totalTime": 0, - "guid": "txn-id", - "traceId": "txn-id", - "priority": 0.500000, - "sampled": false, - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/hello", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 20000}, - Children: []WantTraceSegment{}, - }}, - }, - }}) -} - -func TestTxnTraceNoSegmentsNoAttributes(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - txndata.TxnTrace.Enabled = true - txndata.TxnTrace.StackTraceThreshold = 1 * time.Hour - txndata.TxnTrace.SegmentThreshold = 0 - - acfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(acfg) - - ht := newHarvestTraces() - ht.regular.addTxnTrace(&HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 20 * time.Second, - TotalTime: 30 * time.Second, - FinalName: "WebTransaction/Go/hello", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{ - "totalTime": 30, - "guid": "txn-id", - "traceId": "txn-id", - "priority": 0.500000, - "sampled": false, - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/hello", - RelativeStartMillis: 0, - RelativeStopMillis: 20000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 20000}, - Children: []WantTraceSegment{}, - }}, - }, - }}) -} - -func TestTxnTraceSlowestNodesSaved(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - txndata.TxnTrace.Enabled = true - txndata.TxnTrace.StackTraceThreshold = 1 * time.Hour - txndata.TxnTrace.SegmentThreshold = 0 - txndata.TxnTrace.maxNodes = 5 - - durations := []int{5, 4, 6, 3, 7, 2, 8, 1, 9} - now := start - for _, d := range durations { - s := StartSegment(txndata, thread, now) - now = now.Add(time.Duration(d) * time.Second) - EndBasicSegment(txndata, thread, s, now, strconv.Itoa(d)) - } - - acfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/url", nil) - - ht := newHarvestTraces() - ht.regular.addTxnTrace(&HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 123 * time.Second, - TotalTime: 200 * time.Second, - FinalName: "WebTransaction/Go/hello", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{"request.uri": "/url"}, - Intrinsics: map[string]interface{}{ - "totalTime": 200, - "guid": "txn-id", - "traceId": "txn-id", - "priority": 0.500000, - "sampled": false, - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 123000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/hello", - RelativeStartMillis: 0, - RelativeStopMillis: 123000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 123000}, - Children: []WantTraceSegment{ - { - SegmentName: "Custom/5", - RelativeStartMillis: 0, - RelativeStopMillis: 5000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "Custom/6", - RelativeStartMillis: 9000, - RelativeStopMillis: 15000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "Custom/7", - RelativeStartMillis: 18000, - RelativeStopMillis: 25000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "Custom/8", - RelativeStartMillis: 27000, - RelativeStopMillis: 35000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "Custom/9", - RelativeStartMillis: 36000, - RelativeStopMillis: 45000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - }, - }}, - }, - }}) -} - -func TestTxnTraceSegmentThreshold(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - txndata.TxnTrace.Enabled = true - txndata.TxnTrace.StackTraceThreshold = 1 * time.Hour - txndata.TxnTrace.SegmentThreshold = 7 * time.Second - txndata.TxnTrace.maxNodes = 5 - - durations := []int{5, 4, 6, 3, 7, 2, 8, 1, 9} - now := start - for _, d := range durations { - s := StartSegment(txndata, thread, now) - now = now.Add(time.Duration(d) * time.Second) - EndBasicSegment(txndata, thread, s, now, strconv.Itoa(d)) - } - - acfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/url", nil) - - ht := newHarvestTraces() - ht.regular.addTxnTrace(&HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 123 * time.Second, - TotalTime: 200 * time.Second, - FinalName: "WebTransaction/Go/hello", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id", - Priority: 0.5, - }, - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{"request.uri": "/url"}, - Intrinsics: map[string]interface{}{ - "totalTime": 200, - "guid": "txn-id", - "traceId": "txn-id", - "priority": 0.500000, - "sampled": false, - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 123000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/hello", - RelativeStartMillis: 0, - RelativeStopMillis: 123000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 123000}, - Children: []WantTraceSegment{ - { - SegmentName: "Custom/7", - RelativeStartMillis: 18000, - RelativeStopMillis: 25000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "Custom/8", - RelativeStartMillis: 27000, - RelativeStopMillis: 35000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "Custom/9", - RelativeStartMillis: 36000, - RelativeStopMillis: 45000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - }, - }}, - }, - }}) -} - -func TestEmptyHarvestTraces(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - ht := newHarvestTraces() - js, err := ht.Data("12345", start) - if nil != err || nil != js { - t.Error(string(js), err) - } -} - -func TestLongestTraceSaved(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - txndata.TxnTrace.Enabled = true - - acfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/url", nil) - ht := newHarvestTraces() - - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 3 * time.Second, - TotalTime: 4 * time.Second, - FinalName: "WebTransaction/Go/3", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id-3", - Priority: 0.5, - }, - }, - Trace: txndata.TxnTrace, - }) - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 5 * time.Second, - TotalTime: 6 * time.Second, - FinalName: "WebTransaction/Go/5", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id-5", - Priority: 0.5, - }, - }, - Trace: txndata.TxnTrace, - }) - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 4 * time.Second, - TotalTime: 7 * time.Second, - FinalName: "WebTransaction/Go/4", - Attrs: attr, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "txn-id-4", - Priority: 0.5, - }, - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{{ - MetricName: "WebTransaction/Go/5", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{"request.uri": "/url"}, - Intrinsics: map[string]interface{}{ - "totalTime": 6, - "guid": "txn-id-5", - "traceId": "txn-id-5", - "priority": 0.500000, - "sampled": false, - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 5000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/5", - RelativeStartMillis: 0, - RelativeStopMillis: 5000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 5000}, - Children: []WantTraceSegment{}, - }}, - }, - }}) -} - -func TestTxnTraceStackTraceThreshold(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - thread := &Thread{} - txndata.TxnTrace.Enabled = true - txndata.TxnTrace.StackTraceThreshold = 2 * time.Second - txndata.TxnTrace.SegmentThreshold = 0 - txndata.TxnTrace.maxNodes = 5 - - // below stack trace threshold - t1 := StartSegment(txndata, thread, start.Add(1*time.Second)) - EndBasicSegment(txndata, thread, t1, start.Add(2*time.Second), "t1") - - // not above stack trace threshold w/out params - t2 := StartSegment(txndata, thread, start.Add(2*time.Second)) - EndBasicSegment(txndata, thread, t2, start.Add(4*time.Second), "t2") - - // node above stack trace threshold w/ params - t3 := StartSegment(txndata, thread, start.Add(4*time.Second)) - EndExternalSegment(EndExternalParams{ - TxnData: txndata, - Thread: thread, - Start: t3, - Now: start.Add(6 * time.Second), - URL: parseURL("http://example.com/zip/zap?secret=shhh"), - Logger: logger.ShimLogger{}, - }) - - ht := newHarvestTraces() - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 3 * time.Second, - TotalTime: 4 * time.Second, - FinalName: "WebTransaction/Go/3", - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{ - { - MetricName: "WebTransaction/Go/3", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{"totalTime": 4}, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 3000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/3", - RelativeStartMillis: 0, - RelativeStopMillis: 3000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 3000}, - Children: []WantTraceSegment{ - { - SegmentName: "Custom/t1", - RelativeStartMillis: 1000, - RelativeStopMillis: 2000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "Custom/t2", - RelativeStartMillis: 2000, - RelativeStopMillis: 4000, - Attributes: map[string]interface{}{"backtrace": MatchAnything}, - Children: []WantTraceSegment{}, - }, - { - SegmentName: "External/example.com/http", - RelativeStartMillis: 4000, - RelativeStopMillis: 6000, - Attributes: map[string]interface{}{ - "backtrace": MatchAnything, - "http.url": "http://example.com/zip/zap", - }, - Children: []WantTraceSegment{}, - }, - }, - }}, - }, - }, - }) -} - -func TestTxnTraceSynthetics(t *testing.T) { - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - txndata.TxnTrace.Enabled = true - - acfg := CreateAttributeConfig(sampleAttributeConfigInput, true) - attr := NewAttributes(acfg) - attr.Agent.Add(attributeRequestURI, "/url", nil) - ht := newHarvestTraces() - - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 3 * time.Second, - TotalTime: 4 * time.Second, - FinalName: "WebTransaction/Go/3", - Attrs: attr, - CrossProcess: TxnCrossProcess{ - Type: txnCrossProcessSynthetics, - Synthetics: &cat.SyntheticsHeader{ - ResourceID: "resource", - }, - }, - }, - Trace: txndata.TxnTrace, - }) - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 5 * time.Second, - TotalTime: 6 * time.Second, - FinalName: "WebTransaction/Go/5", - Attrs: attr, - CrossProcess: TxnCrossProcess{ - Type: txnCrossProcessSynthetics, - Synthetics: &cat.SyntheticsHeader{ - ResourceID: "resource", - }, - }, - }, - Trace: txndata.TxnTrace, - }) - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 4 * time.Second, - TotalTime: 5 * time.Second, - FinalName: "WebTransaction/Go/4", - Attrs: attr, - CrossProcess: TxnCrossProcess{ - Type: txnCrossProcessSynthetics, - Synthetics: &cat.SyntheticsHeader{ - ResourceID: "resource", - }, - }, - }, - Trace: txndata.TxnTrace, - }) - - ExpectTxnTraces(t, ht, []WantTxnTrace{ - { - MetricName: "WebTransaction/Go/3", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{"request.uri": "/url"}, - Intrinsics: map[string]interface{}{ - "totalTime": 4, - "synthetics_resource_id": "resource", - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 3000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/3", - RelativeStartMillis: 0, - RelativeStopMillis: 3000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 3000}, - Children: []WantTraceSegment{}, - }}, - }, - }, - { - MetricName: "WebTransaction/Go/5", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{"request.uri": "/url"}, - Intrinsics: map[string]interface{}{ - "totalTime": 6, - "synthetics_resource_id": "resource", - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 5000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/5", - RelativeStartMillis: 0, - RelativeStopMillis: 5000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 5000}, - Children: []WantTraceSegment{}, - }}, - }, - }, - { - MetricName: "WebTransaction/Go/4", - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{"request.uri": "/url"}, - Intrinsics: map[string]interface{}{ - "totalTime": 5, - "synthetics_resource_id": "resource", - }, - Root: WantTraceSegment{ - SegmentName: "ROOT", - RelativeStartMillis: 0, - RelativeStopMillis: 4000, - Attributes: map[string]interface{}{}, - Children: []WantTraceSegment{{ - SegmentName: "WebTransaction/Go/4", - RelativeStartMillis: 0, - RelativeStopMillis: 4000, - Attributes: map[string]interface{}{"exclusive_duration_millis": 4000}, - Children: []WantTraceSegment{}, - }}, - }, - }, - }) -} - -func TestTraceJSON(t *testing.T) { - // Have one test compare exact JSON to ensure that all misc fields (such - // as the trailing `null,false,null,""`) are what we expect. - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - txndata.TxnTrace.Enabled = true - ht := newHarvestTraces() - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 3 * time.Second, - TotalTime: 4 * time.Second, - FinalName: "WebTransaction/Go/trace", - Attrs: nil, - }, - Trace: txndata.TxnTrace, - }) - - expect := `[ - "12345", - [ - [ - 1417136460000000, - 3000, - "WebTransaction/Go/trace", - null, - [0,{},{}, - [ - 0, - 3000, - "ROOT", - {}, - [[0,3000,"WebTransaction/Go/trace",{"exclusive_duration_millis":3000},[]]] - ], - { - "agentAttributes":{}, - "userAttributes":{}, - "intrinsics":{"totalTime":4} - } - ],"",null,false,null,"" - ] - ] -]` - - js, err := ht.Data("12345", start) - if nil != err { - t.Fatal(err) - } - testExpectedJSON(t, expect, string(js)) -} - -func TestTraceCatGUID(t *testing.T) { - // Test catGUID is properly set in outbound json when CAT is enabled - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - txndata.TxnTrace.Enabled = true - ht := newHarvestTraces() - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 3 * time.Second, - TotalTime: 4 * time.Second, - FinalName: "WebTransaction/Go/trace", - Attrs: nil, - CrossProcess: TxnCrossProcess{ - Type: 1, - GUID: "this is guid", - }, - }, - Trace: txndata.TxnTrace, - }) - - expect := `[ - "12345", - [ - [ - 1417136460000000, - 3000, - "WebTransaction/Go/trace", - null, - [0,{},{}, - [ - 0, - 3000, - "ROOT", - {}, - [[0,3000,"WebTransaction/Go/trace",{"exclusive_duration_millis":3000},[]]] - ], - { - "agentAttributes":{}, - "userAttributes":{}, - "intrinsics":{"totalTime":4} - } - ],"this is guid",null,false,null,"" - ] - ] -]` - - js, err := ht.Data("12345", start) - if nil != err { - t.Fatal(err) - } - testExpectedJSON(t, expect, string(js)) -} - -func TestTraceDistributedTracingGUID(t *testing.T) { - // Test catGUID is properly set in outbound json when DT is enabled - start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - txndata := &TxnData{} - txndata.TxnTrace.Enabled = true - ht := newHarvestTraces() - ht.Witness(HarvestTrace{ - TxnEvent: TxnEvent{ - Start: start, - Duration: 3 * time.Second, - TotalTime: 4 * time.Second, - FinalName: "WebTransaction/Go/trace", - Attrs: nil, - BetterCAT: BetterCAT{ - Enabled: true, - ID: "this is guid", - }, - }, - Trace: txndata.TxnTrace, - }) - - expect := `[ - "12345", - [ - [ - 1417136460000000, - 3000, - "WebTransaction/Go/trace", - null, - [0,{},{}, - [ - 0, - 3000, - "ROOT", - {}, - [[0,3000,"WebTransaction/Go/trace",{"exclusive_duration_millis":3000},[]]] - ], - { - "agentAttributes":{}, - "userAttributes":{}, - "intrinsics":{ - "totalTime":4, - "guid":"this is guid", - "traceId":"this is guid", - "priority":0.000000, - "sampled":false - } - } - ],"this is guid",null,false,null,"" - ] - ] -]` - - js, err := ht.Data("12345", start) - if nil != err { - t.Fatal(err) - } - testExpectedJSON(t, expect, string(js)) -} - -func BenchmarkWitnessNode(b *testing.B) { - trace := &TxnTrace{ - Enabled: true, - SegmentThreshold: 0, // save all segments - StackTraceThreshold: 1 * time.Hour, // no stack traces - maxNodes: 100 * 1000, - } - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - end := segmentEnd{ - duration: time.Duration(RandUint32()) * time.Millisecond, - exclusive: 0, - } - trace.witnessNode(end, "myNode", nil, "") - } -} diff --git a/internal/url.go b/internal/url.go deleted file mode 100644 index dbc5ca76b..000000000 --- a/internal/url.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import "net/url" - -// SafeURL removes sensitive information from a URL. -func SafeURL(u *url.URL) string { - if nil == u { - return "" - } - if "" != u.Opaque { - // If the URL is opaque, we cannot be sure if it contains - // sensitive information. - return "" - } - - // Omit user, query, and fragment information for security. - ur := url.URL{ - Scheme: u.Scheme, - Host: u.Host, - Path: u.Path, - } - return ur.String() -} - -// SafeURLFromString removes sensitive information from a URL. -func SafeURLFromString(rawurl string) string { - u, err := url.Parse(rawurl) - if nil != err { - return "" - } - return SafeURL(u) -} - -// HostFromURL returns the URL's host. -func HostFromURL(u *url.URL) string { - if nil == u { - return "" - } - if "" != u.Opaque { - return "opaque" - } - return u.Host -} diff --git a/internal/url_test.go b/internal/url_test.go deleted file mode 100644 index 9fafe8a65..000000000 --- a/internal/url_test.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "net/url" - "strings" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestSafeURLNil(t *testing.T) { - if out := SafeURL(nil); "" != out { - t.Error(out) - } -} - -func TestSafeURL(t *testing.T) { - var testcases []struct { - Testname string `json:"testname"` - Expect string `json:"expected"` - Input string `json:"input"` - } - - err := crossagent.ReadJSON("url_clean.json", &testcases) - if err != nil { - t.Fatal(err) - } - - for _, tc := range testcases { - if strings.Contains(tc.Input, ";") { - // This test case was over defensive: - // http://www.ietf.org/rfc/rfc3986.txt - continue - } - - // Only use testcases which have a scheme, otherwise the urls - // may not be valid and may not be correctly handled by - // url.Parse. - if strings.HasPrefix(tc.Input, "p:") { - u, err := url.Parse(tc.Input) - if nil != err { - t.Error(tc.Testname, tc.Input, err) - continue - } - out := SafeURL(u) - if out != tc.Expect { - t.Error(tc.Testname, tc.Input, tc.Expect) - } - } - } -} - -func TestSafeURLFromString(t *testing.T) { - out := SafeURLFromString(`http://localhost:8000/hello?zip=zap`) - if `http://localhost:8000/hello` != out { - t.Error(out) - } - out = SafeURLFromString("?????") - if "" != out { - t.Error(out) - } -} - -func TestHostFromURL(t *testing.T) { - u, err := url.Parse("http://example.com/zip/zap?secret=shh") - if nil != err { - t.Fatal(err) - } - host := HostFromURL(u) - if host != "example.com" { - t.Error(host) - } - host = HostFromURL(nil) - if host != "" { - t.Error(host) - } - u, err = url.Parse("scheme:opaque") - if nil != err { - t.Fatal(err) - } - host = HostFromURL(u) - if host != "opaque" { - t.Error(host) - } -} diff --git a/internal/utilities.go b/internal/utilities.go deleted file mode 100644 index d993ab6c8..000000000 --- a/internal/utilities.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - "time" -) - -// JSONString assists in logging JSON: Based on the formatter used to log -// Context contents, the contents could be marshalled as JSON or just printed -// directly. -type JSONString string - -// MarshalJSON returns the JSONString unmodified without any escaping. -func (js JSONString) MarshalJSON() ([]byte, error) { - if "" == js { - return []byte("null"), nil - } - return []byte(js), nil -} - -func removeFirstSegment(name string) string { - idx := strings.Index(name, "/") - if -1 == idx { - return name - } - return name[idx+1:] -} - -func timeToFloatSeconds(t time.Time) float64 { - return float64(t.UnixNano()) / float64(1000*1000*1000) -} - -func timeToFloatMilliseconds(t time.Time) float64 { - return float64(t.UnixNano()) / float64(1000*1000) -} - -// FloatSecondsToDuration turns a float64 in seconds into a time.Duration. -func FloatSecondsToDuration(seconds float64) time.Duration { - nanos := seconds * 1000 * 1000 * 1000 - return time.Duration(nanos) * time.Nanosecond -} - -func absTimeDiff(t1, t2 time.Time) time.Duration { - if t1.After(t2) { - return t1.Sub(t2) - } - return t2.Sub(t1) -} - -// CompactJSONString removes the whitespace from a JSON string. This function -// will panic if the string provided is not valid JSON. Thus is must only be -// used in testing code! -func CompactJSONString(js string) string { - buf := new(bytes.Buffer) - if err := json.Compact(buf, []byte(js)); err != nil { - panic(fmt.Errorf("unable to compact JSON: %v", err)) - } - return buf.String() -} - -// GetContentLengthFromHeader gets the content length from a HTTP header, or -1 -// if no content length is available. -func GetContentLengthFromHeader(h http.Header) int64 { - if cl := h.Get("Content-Length"); cl != "" { - if contentLength, err := strconv.ParseInt(cl, 10, 64); err == nil { - return contentLength - } - } - - return -1 -} - -// StringLengthByteLimit truncates strings using a byte-limit boundary and -// avoids terminating in the middle of a multibyte character. -func StringLengthByteLimit(str string, byteLimit int) string { - if len(str) <= byteLimit { - return str - } - - limitIndex := 0 - for pos := range str { - if pos > byteLimit { - break - } - limitIndex = pos - } - return str[0:limitIndex] -} - -func timeFromUnixMilliseconds(millis uint64) time.Time { - secs := int64(millis) / 1000 - msecsRemaining := int64(millis) % 1000 - nsecsRemaining := msecsRemaining * (1000 * 1000) - return time.Unix(secs, nsecsRemaining) -} - -// TimeToUnixMilliseconds converts a time into a Unix timestamp in millisecond -// units. -func TimeToUnixMilliseconds(tm time.Time) uint64 { - return uint64(tm.UnixNano()) / uint64(1000*1000) -} - -// MinorVersion takes a given version string and returns only the major and -// minor portions of it. If the input is malformed, it returns the input -// untouched. -func MinorVersion(v string) string { - split := strings.SplitN(v, ".", 3) - if len(split) < 2 { - return v - } - return split[0] + "." + split[1] -} diff --git a/internal/utilities_test.go b/internal/utilities_test.go deleted file mode 100644 index 3c3cbded8..000000000 --- a/internal/utilities_test.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package internal - -import ( - "net/http" - "testing" - "time" -) - -func TestRemoveFirstSegment(t *testing.T) { - testcases := []struct { - input string - expected string - }{ - {input: "no_seperators", expected: "no_seperators"}, - {input: "heyo/zip/zap", expected: "zip/zap"}, - {input: "ends_in_slash/", expected: ""}, - {input: "☃☃☃/✓✓✓/heyo", expected: "✓✓✓/heyo"}, - {input: "☃☃☃/", expected: ""}, - {input: "/", expected: ""}, - {input: "", expected: ""}, - } - - for _, tc := range testcases { - out := removeFirstSegment(tc.input) - if out != tc.expected { - t.Fatal(tc.input, out, tc.expected) - } - } -} - -func TestFloatSecondsToDuration(t *testing.T) { - if d := FloatSecondsToDuration(0.123); d != 123*time.Millisecond { - t.Error(d) - } - if d := FloatSecondsToDuration(456.0); d != 456*time.Second { - t.Error(d) - } -} - -func TestAbsTimeDiff(t *testing.T) { - diff := 5 * time.Second - before := time.Now() - after := before.Add(5 * time.Second) - - if out := absTimeDiff(before, after); out != diff { - t.Error(out, diff) - } - if out := absTimeDiff(after, before); out != diff { - t.Error(out, diff) - } - if out := absTimeDiff(after, after); out != 0 { - t.Error(out) - } -} - -func TestTimeToFloatMilliseconds(t *testing.T) { - tm := time.Unix(123, 456789000) - if ms := timeToFloatMilliseconds(tm); ms != 123456.789 { - t.Error(ms) - } -} - -func TestCompactJSON(t *testing.T) { - in := ` - { "zip": 1}` - out := CompactJSONString(in) - if out != `{"zip":1}` { - t.Fatal(in, out) - } -} - -func TestGetContentLengthFromHeader(t *testing.T) { - // Nil header. - if cl := GetContentLengthFromHeader(nil); cl != -1 { - t.Errorf("unexpected content length: expected -1; got %d", cl) - } - - // Empty header. - header := make(http.Header) - if cl := GetContentLengthFromHeader(header); cl != -1 { - t.Errorf("unexpected content length: expected -1; got %d", cl) - } - - // Invalid header. - header.Set("Content-Length", "foo") - if cl := GetContentLengthFromHeader(header); cl != -1 { - t.Errorf("unexpected content length: expected -1; got %d", cl) - } - - // Zero header. - header.Set("Content-Length", "0") - if cl := GetContentLengthFromHeader(header); cl != 0 { - t.Errorf("unexpected content length: expected 0; got %d", cl) - } - - // Valid, non-zero header. - header.Set("Content-Length", "1024") - if cl := GetContentLengthFromHeader(header); cl != 1024 { - t.Errorf("unexpected content length: expected 1024; got %d", cl) - } -} - -func TestStringLengthByteLimit(t *testing.T) { - testcases := []struct { - input string - limit int - expect string - }{ - {"", 255, ""}, - {"awesome", -1, ""}, - {"awesome", 0, ""}, - {"awesome", 1, "a"}, - {"awesome", 7, "awesome"}, - {"awesome", 20, "awesome"}, - {"日本\x80語", 10, "日本\x80語"}, // bad unicode - {"日本", 1, ""}, - {"日本", 2, ""}, - {"日本", 3, "日"}, - {"日本", 4, "日"}, - {"日本", 5, "日"}, - {"日本", 6, "日本"}, - {"日本", 7, "日本"}, - } - - for _, tc := range testcases { - out := StringLengthByteLimit(tc.input, tc.limit) - if out != tc.expect { - t.Error(tc.input, tc.limit, tc.expect, out) - } - } -} - -func TestTimeToAndFromUnixMilliseconds(t *testing.T) { - t1 := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) - millis := TimeToUnixMilliseconds(t1) - if millis != 1417136460000 { - t.Fatal(millis) - } - t2 := timeFromUnixMilliseconds(millis) - if t1.UnixNano() != t2.UnixNano() { - t.Fatal(t1, t2) - } -} - -func TestMinorVersion(t *testing.T) { - testcases := []struct { - input string - expect string - }{ - {"go1.13", "go1.13"}, - {"go1.13.1", "go1.13"}, - {"go1.13.1.0", "go1.13"}, - {"purple", "purple"}, - } - - for _, test := range testcases { - if actual := MinorVersion(test.input); actual != test.expect { - t.Errorf("incorrect result: expect=%s actual=%s", test.expect, actual) - } - } -} diff --git a/internal/utilization/addresses.go b/internal/utilization/addresses.go deleted file mode 100644 index 791b52726..000000000 --- a/internal/utilization/addresses.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "fmt" - "net" -) - -func nonlocalIPAddressesByInterface() (map[string][]string, error) { - ifaces, err := net.Interfaces() - if err != nil { - return nil, err - } - ips := make(map[string][]string, len(ifaces)) - for _, ifc := range ifaces { - addrs, err := ifc.Addrs() - if err != nil { - continue - } - for _, addr := range addrs { - var ip net.IP - switch iptype := addr.(type) { - case *net.IPAddr: - ip = iptype.IP - case *net.IPNet: - ip = iptype.IP - case *net.TCPAddr: - ip = iptype.IP - case *net.UDPAddr: - ip = iptype.IP - } - if nil != ip && !ip.IsLoopback() && !ip.IsUnspecified() { - ips[ifc.Name] = append(ips[ifc.Name], ip.String()) - } - } - } - return ips, nil -} - -// utilizationIPs gathers IP address which may help identify this entity. This -// code chooses all IPs from the interface which contains the IP of a UDP -// connection with NR. This approach has the following advantages: -// * Matches the behavior of the Java agent. -// * Reports fewer IPs to lower linking burden on infrastructure backend. -// * The UDP connection interface is more likely to contain unique external IPs. -func utilizationIPs() ([]string, error) { - // Port choice designed to match - // https://source.datanerd.us/java-agent/java_agent/blob/master/newrelic-agent/src/main/java/com/newrelic/agent/config/Hostname.java#L110 - conn, err := net.Dial("udp", "newrelic.com:10002") - if err != nil { - return nil, err - } - defer conn.Close() - - addr, ok := conn.LocalAddr().(*net.UDPAddr) - - if !ok || nil == addr || addr.IP.IsLoopback() || addr.IP.IsUnspecified() { - return nil, fmt.Errorf("unexpected connection address: %v", conn.LocalAddr()) - } - outboundIP := addr.IP.String() - - ipsByInterface, err := nonlocalIPAddressesByInterface() - if err != nil { - return nil, err - } - for _, ips := range ipsByInterface { - for _, ip := range ips { - if ip == outboundIP { - return ips, nil - } - } - } - return nil, nil -} diff --git a/internal/utilization/aws.go b/internal/utilization/aws.go deleted file mode 100644 index 1cd8eeac5..000000000 --- a/internal/utilization/aws.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -const ( - awsHostname = "169.254.169.254" - awsEndpointPath = "/2016-09-02/dynamic/instance-identity/document" - awsEndpoint = "http://" + awsHostname + awsEndpointPath -) - -type aws struct { - InstanceID string `json:"instanceId,omitempty"` - InstanceType string `json:"instanceType,omitempty"` - AvailabilityZone string `json:"availabilityZone,omitempty"` -} - -func gatherAWS(util *Data, client *http.Client) error { - aws, err := getAWS(client) - if err != nil { - // Only return the error here if it is unexpected to prevent - // warning customers who aren't running AWS about a timeout. - if _, ok := err.(unexpectedAWSErr); ok { - return err - } - return nil - } - util.Vendors.AWS = aws - - return nil -} - -type unexpectedAWSErr struct{ e error } - -func (e unexpectedAWSErr) Error() string { - return fmt.Sprintf("unexpected AWS error: %v", e.e) -} - -func getAWS(client *http.Client) (*aws, error) { - response, err := client.Get(awsEndpoint) - if err != nil { - // No unexpectedAWSErr here: A timeout is usually going to - // happen. - return nil, err - } - defer response.Body.Close() - - if response.StatusCode != 200 { - return nil, unexpectedAWSErr{e: fmt.Errorf("response code %d", response.StatusCode)} - } - - data, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, unexpectedAWSErr{e: err} - } - a := &aws{} - if err := json.Unmarshal(data, a); err != nil { - return nil, unexpectedAWSErr{e: err} - } - - if err := a.validate(); err != nil { - return nil, unexpectedAWSErr{e: err} - } - - return a, nil -} - -func (a *aws) validate() (err error) { - a.InstanceID, err = normalizeValue(a.InstanceID) - if err != nil { - return fmt.Errorf("invalid instance ID: %v", err) - } - - a.InstanceType, err = normalizeValue(a.InstanceType) - if err != nil { - return fmt.Errorf("invalid instance type: %v", err) - } - - a.AvailabilityZone, err = normalizeValue(a.AvailabilityZone) - if err != nil { - return fmt.Errorf("invalid availability zone: %v", err) - } - - return -} diff --git a/internal/utilization/aws_test.go b/internal/utilization/aws_test.go deleted file mode 100644 index 33f5a3bc5..000000000 --- a/internal/utilization/aws_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "net/http" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestCrossAgentAWS(t *testing.T) { - var testCases []testCase - - err := crossagent.ReadJSON("utilization_vendor_specific/aws.json", &testCases) - if err != nil { - t.Fatalf("reading aws.json failed: %v", err) - } - - for _, testCase := range testCases { - client := &http.Client{ - Transport: &mockTransport{ - t: t, - responses: testCase.URIs, - }, - } - - aws, err := getAWS(client) - - if testCase.ExpectedVendorsHash.AWS == nil { - if err == nil { - t.Fatalf("%s: expected error; got nil", testCase.TestName) - } - } else { - if err != nil { - t.Fatalf("%s: expected no error; got %v", testCase.TestName, err) - } - - if aws.InstanceID != testCase.ExpectedVendorsHash.AWS.InstanceID { - t.Fatalf("%s: instanceId incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.AWS.InstanceID, aws.InstanceID) - } - - if aws.InstanceType != testCase.ExpectedVendorsHash.AWS.InstanceType { - t.Fatalf("%s: instanceType incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.AWS.InstanceType, aws.InstanceType) - } - - if aws.AvailabilityZone != testCase.ExpectedVendorsHash.AWS.AvailabilityZone { - t.Fatalf("%s: availabilityZone incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.AWS.AvailabilityZone, aws.AvailabilityZone) - } - } - } -} diff --git a/internal/utilization/azure.go b/internal/utilization/azure.go deleted file mode 100644 index b2e1846be..000000000 --- a/internal/utilization/azure.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" -) - -const ( - azureHostname = "169.254.169.254" - azureEndpointPath = "/metadata/instance/compute?api-version=2017-03-01" - azureEndpoint = "http://" + azureHostname + azureEndpointPath -) - -type azure struct { - Location string `json:"location,omitempty"` - Name string `json:"name,omitempty"` - VMID string `json:"vmId,omitempty"` - VMSize string `json:"vmSize,omitempty"` -} - -func gatherAzure(util *Data, client *http.Client) error { - az, err := getAzure(client) - if err != nil { - // Only return the error here if it is unexpected to prevent - // warning customers who aren't running Azure about a timeout. - if _, ok := err.(unexpectedAzureErr); ok { - return err - } - return nil - } - util.Vendors.Azure = az - - return nil -} - -type unexpectedAzureErr struct{ e error } - -func (e unexpectedAzureErr) Error() string { - return fmt.Sprintf("unexpected Azure error: %v", e.e) -} - -func getAzure(client *http.Client) (*azure, error) { - req, err := http.NewRequest("GET", azureEndpoint, nil) - if err != nil { - return nil, err - } - req.Header.Add("Metadata", "true") - - response, err := client.Do(req) - if err != nil { - // No unexpectedAzureErr here: a timeout isusually going to - // happen. - return nil, err - } - defer response.Body.Close() - - if response.StatusCode != 200 { - return nil, unexpectedAzureErr{e: fmt.Errorf("response code %d", response.StatusCode)} - } - - data, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, unexpectedAzureErr{e: err} - } - - az := &azure{} - if err := json.Unmarshal(data, az); err != nil { - return nil, unexpectedAzureErr{e: err} - } - - if err := az.validate(); err != nil { - return nil, unexpectedAzureErr{e: err} - } - - return az, nil -} - -func (az *azure) validate() (err error) { - az.Location, err = normalizeValue(az.Location) - if err != nil { - return fmt.Errorf("Invalid location: %v", err) - } - - az.Name, err = normalizeValue(az.Name) - if err != nil { - return fmt.Errorf("Invalid name: %v", err) - } - - az.VMID, err = normalizeValue(az.VMID) - if err != nil { - return fmt.Errorf("Invalid VM ID: %v", err) - } - - az.VMSize, err = normalizeValue(az.VMSize) - if err != nil { - return fmt.Errorf("Invalid VM size: %v", err) - } - - return -} diff --git a/internal/utilization/azure_test.go b/internal/utilization/azure_test.go deleted file mode 100644 index 9ca6b4ed8..000000000 --- a/internal/utilization/azure_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "net/http" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestCrossAgentAzure(t *testing.T) { - var testCases []testCase - - err := crossagent.ReadJSON("utilization_vendor_specific/azure.json", &testCases) - if err != nil { - t.Fatalf("reading azure.json failed: %v", err) - } - - for _, testCase := range testCases { - client := &http.Client{ - Transport: &mockTransport{ - t: t, - responses: testCase.URIs, - }, - } - - azure, err := getAzure(client) - - if testCase.ExpectedVendorsHash.Azure == nil { - if err == nil { - t.Fatalf("%s: expected error; got nil", testCase.TestName) - } - } else { - if err != nil { - t.Fatalf("%s: expected no error; got %v", testCase.TestName, err) - } - - if azure.Location != testCase.ExpectedVendorsHash.Azure.Location { - t.Fatalf("%s: Location incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.Azure.Location, azure.Location) - } - - if azure.Name != testCase.ExpectedVendorsHash.Azure.Name { - t.Fatalf("%s: Name incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.Azure.Name, azure.Name) - } - - if azure.VMID != testCase.ExpectedVendorsHash.Azure.VMID { - t.Fatalf("%s: VMID incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.Azure.VMID, azure.VMID) - } - - if azure.VMSize != testCase.ExpectedVendorsHash.Azure.VMSize { - t.Fatalf("%s: VMSize incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.Azure.VMSize, azure.VMSize) - } - } - } -} diff --git a/internal/utilization/fqdn.go b/internal/utilization/fqdn.go deleted file mode 100644 index 211312fa7..000000000 --- a/internal/utilization/fqdn.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.8 - -package utilization - -import ( - "context" - "net" - "strings" -) - -func lookupAddr(addr string) ([]string, error) { - ctx, cancel := context.WithTimeout(context.Background(), lookupAddrTimeout) - defer cancel() - - r := &net.Resolver{} - - return r.LookupAddr(ctx, addr) -} - -func getFQDN(candidateIPs []string) string { - for _, ip := range candidateIPs { - names, _ := lookupAddr(ip) - if len(names) > 0 { - return strings.TrimSuffix(names[0], ".") - } - } - return "" -} diff --git a/internal/utilization/fqdn_pre18.go b/internal/utilization/fqdn_pre18.go deleted file mode 100644 index 44960fe00..000000000 --- a/internal/utilization/fqdn_pre18.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build !go1.8 - -package utilization - -// net.Resolver.LookupAddr was added in Go 1.8, and net.LookupAddr does not have -// a controllable timeout, so we skip the optional full_hostname on pre 1.8 -// versions. - -func getFQDN(candidateIPs []string) string { - return "" -} diff --git a/internal/utilization/gcp.go b/internal/utilization/gcp.go deleted file mode 100644 index b4fc4208e..000000000 --- a/internal/utilization/gcp.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "strings" -) - -const ( - gcpHostname = "metadata.google.internal" - gcpEndpointPath = "/computeMetadata/v1/instance/?recursive=true" - gcpEndpoint = "http://" + gcpHostname + gcpEndpointPath -) - -func gatherGCP(util *Data, client *http.Client) error { - gcp, err := getGCP(client) - if err != nil { - // Only return the error here if it is unexpected to prevent - // warning customers who aren't running GCP about a timeout. - if _, ok := err.(unexpectedGCPErr); ok { - return err - } - return nil - } - util.Vendors.GCP = gcp - - return nil -} - -// numericString is used rather than json.Number because we want the output when -// marshalled to be a string, rather than a number. -type numericString string - -func (ns *numericString) MarshalJSON() ([]byte, error) { - return json.Marshal(ns.String()) -} - -func (ns *numericString) String() string { - return string(*ns) -} - -func (ns *numericString) UnmarshalJSON(data []byte) error { - var n int64 - - // Try to unmarshal as an integer first. - if err := json.Unmarshal(data, &n); err == nil { - *ns = numericString(fmt.Sprintf("%d", n)) - return nil - } - - // Otherwise, unmarshal as a string, and verify that it's numeric (for our - // definition of numeric, which is actually integral). - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - - for _, r := range s { - if r < '0' || r > '9' { - return fmt.Errorf("invalid numeric character: %c", r) - } - } - - *ns = numericString(s) - return nil -} - -type gcp struct { - ID numericString `json:"id"` - MachineType string `json:"machineType,omitempty"` - Name string `json:"name,omitempty"` - Zone string `json:"zone,omitempty"` -} - -type unexpectedGCPErr struct{ e error } - -func (e unexpectedGCPErr) Error() string { - return fmt.Sprintf("unexpected GCP error: %v", e.e) -} - -func getGCP(client *http.Client) (*gcp, error) { - // GCP's metadata service requires a Metadata-Flavor header because... hell, I - // don't know, maybe they really like Guy Fieri? - req, err := http.NewRequest("GET", gcpEndpoint, nil) - if err != nil { - return nil, err - } - req.Header.Add("Metadata-Flavor", "Google") - - response, err := client.Do(req) - if err != nil { - return nil, err - } - defer response.Body.Close() - - if response.StatusCode != 200 { - return nil, unexpectedGCPErr{e: fmt.Errorf("response code %d", response.StatusCode)} - } - - data, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, unexpectedGCPErr{e: err} - } - - g := &gcp{} - if err := json.Unmarshal(data, g); err != nil { - return nil, unexpectedGCPErr{e: err} - } - - if err := g.validate(); err != nil { - return nil, unexpectedGCPErr{e: err} - } - - return g, nil -} - -func (g *gcp) validate() (err error) { - id, err := normalizeValue(g.ID.String()) - if err != nil { - return fmt.Errorf("Invalid ID: %v", err) - } - g.ID = numericString(id) - - mt, err := normalizeValue(g.MachineType) - if err != nil { - return fmt.Errorf("Invalid machine type: %v", err) - } - g.MachineType = stripGCPPrefix(mt) - - g.Name, err = normalizeValue(g.Name) - if err != nil { - return fmt.Errorf("Invalid name: %v", err) - } - - zone, err := normalizeValue(g.Zone) - if err != nil { - return fmt.Errorf("Invalid zone: %v", err) - } - g.Zone = stripGCPPrefix(zone) - - return -} - -// We're only interested in the last element of slash separated paths for the -// machine type and zone values, so this function handles stripping the parts -// we don't need. -func stripGCPPrefix(s string) string { - parts := strings.Split(s, "/") - return parts[len(parts)-1] -} diff --git a/internal/utilization/gcp_test.go b/internal/utilization/gcp_test.go deleted file mode 100644 index 3e281d056..000000000 --- a/internal/utilization/gcp_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "net/http" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestCrossAgentGCP(t *testing.T) { - var testCases []testCase - - err := crossagent.ReadJSON("utilization_vendor_specific/gcp.json", &testCases) - if err != nil { - t.Fatalf("reading gcp.json failed: %v", err) - } - - for _, testCase := range testCases { - client := &http.Client{ - Transport: &mockTransport{ - t: t, - responses: testCase.URIs, - }, - } - - gcp, err := getGCP(client) - - if testCase.ExpectedVendorsHash.GCP == nil { - if err == nil { - t.Fatalf("%s: expected error; got nil", testCase.TestName) - } - } else { - if err != nil { - t.Fatalf("%s: expected no error; got %v", testCase.TestName, err) - } - - if gcp.ID != testCase.ExpectedVendorsHash.GCP.ID { - t.Fatalf("%s: ID incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.GCP.ID, gcp.ID) - } - - if gcp.MachineType != testCase.ExpectedVendorsHash.GCP.MachineType { - t.Fatalf("%s: MachineType incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.GCP.MachineType, gcp.MachineType) - } - - if gcp.Name != testCase.ExpectedVendorsHash.GCP.Name { - t.Fatalf("%s: Name incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.GCP.Name, gcp.Name) - } - - if gcp.Zone != testCase.ExpectedVendorsHash.GCP.Zone { - t.Fatalf("%s: Zone incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.GCP.Zone, gcp.Zone) - } - } - } -} - -func TestStripGCPPrefix(t *testing.T) { - testCases := []struct { - input string - expected string - }{ - {"foo/bar", "bar"}, - {"/foo/bar", "bar"}, - {"/foo/bar/", ""}, - {"foo", "foo"}, - {"", ""}, - } - - for _, tc := range testCases { - actual := stripGCPPrefix(tc.input) - if tc.expected != actual { - t.Fatalf("input: %s; expected: %s; actual: %s", tc.input, tc.expected, actual) - } - } -} diff --git a/internal/utilization/pcf.go b/internal/utilization/pcf.go deleted file mode 100644 index f39c9646a..000000000 --- a/internal/utilization/pcf.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "errors" - "fmt" - "net/http" - "os" -) - -type pcf struct { - InstanceGUID string `json:"cf_instance_guid,omitempty"` - InstanceIP string `json:"cf_instance_ip,omitempty"` - MemoryLimit string `json:"memory_limit,omitempty"` -} - -func gatherPCF(util *Data, _ *http.Client) error { - pcf, err := getPCF(os.Getenv) - if err != nil { - // Only return the error here if it is unexpected to prevent - // warning customers who aren't running PCF about a timeout. - if _, ok := err.(unexpectedPCFErr); ok { - return err - } - return nil - } - util.Vendors.PCF = pcf - - return nil -} - -type unexpectedPCFErr struct{ e error } - -func (e unexpectedPCFErr) Error() string { - return fmt.Sprintf("unexpected PCF error: %v", e.e) -} - -var ( - errNoPCFVariables = errors.New("no PCF environment variables present") -) - -func getPCF(initializer func(key string) string) (*pcf, error) { - p := &pcf{} - - p.InstanceGUID = initializer("CF_INSTANCE_GUID") - p.InstanceIP = initializer("CF_INSTANCE_IP") - p.MemoryLimit = initializer("MEMORY_LIMIT") - - if "" == p.InstanceGUID && "" == p.InstanceIP && "" == p.MemoryLimit { - return nil, errNoPCFVariables - } - - if err := p.validate(); err != nil { - return nil, unexpectedPCFErr{e: err} - } - - return p, nil -} - -func (pcf *pcf) validate() (err error) { - pcf.InstanceGUID, err = normalizeValue(pcf.InstanceGUID) - if err != nil { - return fmt.Errorf("Invalid instance GUID: %v", err) - } - - pcf.InstanceIP, err = normalizeValue(pcf.InstanceIP) - if err != nil { - return fmt.Errorf("Invalid instance IP: %v", err) - } - - pcf.MemoryLimit, err = normalizeValue(pcf.MemoryLimit) - if err != nil { - return fmt.Errorf("Invalid memory limit: %v", err) - } - - if pcf.InstanceGUID == "" || pcf.InstanceIP == "" || pcf.MemoryLimit == "" { - err = errors.New("One or more environment variables are unavailable") - } - - return -} diff --git a/internal/utilization/pcf_test.go b/internal/utilization/pcf_test.go deleted file mode 100644 index 172cdba0e..000000000 --- a/internal/utilization/pcf_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestCrossAgentPCF(t *testing.T) { - var testCases []testCase - - err := crossagent.ReadJSON("utilization_vendor_specific/pcf.json", &testCases) - if err != nil { - t.Fatalf("reading pcf.json failed: %v", err) - } - - for _, testCase := range testCases { - pcf, err := getPCF(func(key string) string { - resp := testCase.EnvVars[key] - if resp.Timeout { - return "" - } - return resp.Response - }) - - if testCase.ExpectedVendorsHash.PCF == nil { - if err == nil { - t.Fatalf("%s: expected error; got nil", testCase.TestName) - } - } else { - if err != nil { - t.Fatalf("%s: expected no error; got %v", testCase.TestName, err) - } - - if pcf.InstanceGUID != testCase.ExpectedVendorsHash.PCF.InstanceGUID { - t.Fatalf("%s: InstanceGUID incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.PCF.InstanceGUID, pcf.InstanceGUID) - } - - if pcf.InstanceIP != testCase.ExpectedVendorsHash.PCF.InstanceIP { - t.Fatalf("%s: InstanceIP incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.PCF.InstanceIP, pcf.InstanceIP) - } - - if pcf.MemoryLimit != testCase.ExpectedVendorsHash.PCF.MemoryLimit { - t.Fatalf("%s: MemoryLimit incorrect; expected: %s; got: %s", testCase.TestName, testCase.ExpectedVendorsHash.PCF.MemoryLimit, pcf.MemoryLimit) - } - } - } -} diff --git a/internal/utilization/provider.go b/internal/utilization/provider.go deleted file mode 100644 index 03bcb64e0..000000000 --- a/internal/utilization/provider.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "fmt" - "strings" - "time" -) - -// Helper constants, functions, and types common to multiple providers are -// contained in this file. - -// Constants from the spec. -const ( - maxFieldValueSize = 255 // The maximum value size, in bytes. - providerTimeout = 1 * time.Second // The maximum time a HTTP provider may block. - lookupAddrTimeout = 500 * time.Millisecond -) - -type validationError struct{ e error } - -func (a validationError) Error() string { - return a.e.Error() -} - -func isValidationError(e error) bool { - _, is := e.(validationError) - return is -} - -// This function normalises string values per the utilization spec. -func normalizeValue(s string) (string, error) { - out := strings.TrimSpace(s) - - bytes := []byte(out) - if len(bytes) > maxFieldValueSize { - return "", validationError{fmt.Errorf("response is too long: got %d; expected <=%d", len(bytes), maxFieldValueSize)} - } - - for i, r := range out { - if !isAcceptableRune(r) { - return "", validationError{fmt.Errorf("bad character %x at position %d in response", r, i)} - } - } - - return out, nil -} - -func isAcceptableRune(r rune) bool { - switch r { - case 0xFFFD: - return false // invalid UTF-8 - case '_', ' ', '/', '.', '-': - return true - default: - return r > 0x7f || // still allows some invalid UTF-8, but that's the spec. - ('0' <= r && r <= '9') || - ('a' <= r && r <= 'z') || - ('A' <= r && r <= 'Z') - } -} diff --git a/internal/utilization/provider_test.go b/internal/utilization/provider_test.go deleted file mode 100644 index 1b5c1fbcc..000000000 --- a/internal/utilization/provider_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "bytes" - "encoding/json" - "errors" - "net/http" - "testing" -) - -// Cross agent test types common to each provider's set of test cases. -type testCase struct { - TestName string `json:"testname"` - URIs map[string]jsonResponse `json:"uri"` - EnvVars map[string]envResponse `json:"env_vars"` - ExpectedVendorsHash vendors `json:"expected_vendors_hash"` - ExpectedMetrics map[string]metric `json:"expected_metrics"` -} - -type envResponse struct { - Response string `json:"response"` - Timeout bool `json:"timeout"` -} - -type jsonResponse struct { - Response json.RawMessage `json:"response"` - Timeout bool `json:"timeout"` -} - -type metric struct { - CallCount int `json:"call_count"` -} - -var errTimeout = errors.New("timeout") - -type mockTransport struct { - t *testing.T - responses map[string]jsonResponse -} - -type mockBody struct { - bytes.Reader - closed bool - t *testing.T -} - -func (m *mockTransport) RoundTrip(r *http.Request) (*http.Response, error) { - for match, response := range m.responses { - if r.URL.String() == match { - return m.respond(response) - } - } - - m.t.Errorf("Unknown request URI: %s", r.URL.String()) - return nil, nil -} - -func (m *mockTransport) respond(resp jsonResponse) (*http.Response, error) { - if resp.Timeout { - return nil, errTimeout - } - - return &http.Response{ - Status: "200 OK", - StatusCode: 200, - Body: &mockBody{ - t: m.t, - Reader: *bytes.NewReader(resp.Response), - }, - }, nil -} - -// This function is included simply so that http.Client doesn't complain. -func (m *mockTransport) CancelRequest(r *http.Request) {} - -func (m *mockBody) Close() error { - if m.closed { - m.t.Error("Close of already closed connection!") - } - - m.closed = true - return nil -} - -func (m *mockBody) ensureClosed() { - if !m.closed { - m.t.Error("Connection was not closed") - } -} - -func TestNormaliseValue(t *testing.T) { - testCases := []struct { - name string - input string - expected string - isError bool - }{ - { - name: "Valid - empty", - input: "", - expected: "", - isError: false, - }, - { - name: "Valid - symbols", - input: ". /-_", - expected: ". /-_", - isError: false, - }, - { - name: "Valid - string", - input: "simplesentence", - expected: "simplesentence", - isError: false, - }, - { - name: "Invalid - More than 255", - input: `256256256256256256256256256256256256256256256256256256256256 - 256256256256256256256256256256256256256256256256256256256256256256256256 - 256256256256256256256256256256256256256256256256256256256256256256256256 - 2562562562562562562562562562562562562562562562562562`, - expected: "", - isError: true, - }, - } - - for _, tc := range testCases { - actual, err := normalizeValue(tc.input) - - if tc.isError && err == nil { - t.Fatalf("%s: expected error; got nil", tc.name) - } else if !tc.isError { - if err != nil { - t.Fatalf("%s: expected not error; got: %v", tc.name, err) - } - if tc.expected != actual { - t.Fatalf("%s: expected: %s; got: %s", tc.name, tc.expected, actual) - } - } - } -} diff --git a/internal/utilization/utilization.go b/internal/utilization/utilization.go deleted file mode 100644 index a0fcbcb4a..000000000 --- a/internal/utilization/utilization.go +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Package utilization implements the Utilization spec, available at -// https://source.datanerd.us/agents/agent-specs/blob/master/Utilization.md -// -package utilization - -import ( - "net/http" - "os" - "runtime" - "sync" - - "github.com/newrelic/go-agent/internal/logger" - "github.com/newrelic/go-agent/internal/sysinfo" -) - -const ( - metadataVersion = 5 -) - -// Config controls the behavior of utilization information capture. -type Config struct { - DetectAWS bool - DetectAzure bool - DetectGCP bool - DetectPCF bool - DetectDocker bool - DetectKubernetes bool - LogicalProcessors int - TotalRAMMIB int - BillingHostname string -} - -type override struct { - LogicalProcessors *int `json:"logical_processors,omitempty"` - TotalRAMMIB *int `json:"total_ram_mib,omitempty"` - BillingHostname string `json:"hostname,omitempty"` -} - -// Data contains utilization system information. -type Data struct { - MetadataVersion int `json:"metadata_version"` - // Although `runtime.NumCPU()` will never fail, this field is a pointer - // to facilitate the cross agent tests. - LogicalProcessors *int `json:"logical_processors"` - RAMMiB *uint64 `json:"total_ram_mib"` - Hostname string `json:"hostname"` - FullHostname string `json:"full_hostname,omitempty"` - Addresses []string `json:"ip_address,omitempty"` - BootID string `json:"boot_id,omitempty"` - Config *override `json:"config,omitempty"` - Vendors *vendors `json:"vendors,omitempty"` -} - -var ( - sampleRAMMib = uint64(1024) - sampleLogicProc = int(16) - // SampleData contains sample utilization data useful for testing. - SampleData = Data{ - MetadataVersion: metadataVersion, - LogicalProcessors: &sampleLogicProc, - RAMMiB: &sampleRAMMib, - Hostname: "my-hostname", - } -) - -type docker struct { - ID string `json:"id,omitempty"` -} - -type kubernetes struct { - Host string `json:"kubernetes_service_host"` -} - -type vendors struct { - AWS *aws `json:"aws,omitempty"` - Azure *azure `json:"azure,omitempty"` - GCP *gcp `json:"gcp,omitempty"` - PCF *pcf `json:"pcf,omitempty"` - Docker *docker `json:"docker,omitempty"` - Kubernetes *kubernetes `json:"kubernetes,omitempty"` -} - -func (v *vendors) isEmpty() bool { - return nil == v || *v == vendors{} -} - -func overrideFromConfig(config Config) *override { - ov := &override{} - - if 0 != config.LogicalProcessors { - x := config.LogicalProcessors - ov.LogicalProcessors = &x - } - if 0 != config.TotalRAMMIB { - x := config.TotalRAMMIB - ov.TotalRAMMIB = &x - } - ov.BillingHostname = config.BillingHostname - - if "" == ov.BillingHostname && - nil == ov.LogicalProcessors && - nil == ov.TotalRAMMIB { - ov = nil - } - return ov -} - -// Gather gathers system utilization data. -func Gather(config Config, lg logger.Logger) *Data { - client := &http.Client{ - Timeout: providerTimeout, - } - return gatherWithClient(config, lg, client) -} - -func gatherWithClient(config Config, lg logger.Logger, client *http.Client) *Data { - var wg sync.WaitGroup - - cpu := runtime.NumCPU() - uDat := &Data{ - MetadataVersion: metadataVersion, - LogicalProcessors: &cpu, - Vendors: &vendors{}, - } - - warnGatherError := func(datatype string, err error) { - lg.Debug("error gathering utilization data", map[string]interface{}{ - "error": err.Error(), - "datatype": datatype, - }) - } - - // Gather IPs before spawning goroutines since the IPs are used in - // gathering full hostname. - if ips, err := utilizationIPs(); nil == err { - uDat.Addresses = ips - } else { - warnGatherError("addresses", err) - } - - // This closure allows us to run each gather function in a separate goroutine - // and wait for them at the end by closing over the wg WaitGroup we - // instantiated at the start of the function. - goGather := func(datatype string, gather func(*Data, *http.Client) error) { - wg.Add(1) - go func() { - // Note that locking around util is not necessary since - // WaitGroup provides acts as a memory barrier: - // https://groups.google.com/d/msg/golang-nuts/5oHzhzXCcmM/utEwIAApCQAJ - // Thus this code is fine as long as each routine is - // modifying a different field of util. - defer wg.Done() - if err := gather(uDat, client); err != nil { - warnGatherError(datatype, err) - } - }() - } - - // Kick off gathering which requires network calls in goroutines. - - if config.DetectAWS { - goGather("aws", gatherAWS) - } - - if config.DetectAzure { - goGather("azure", gatherAzure) - } - - if config.DetectPCF { - goGather("pcf", gatherPCF) - } - - if config.DetectGCP { - goGather("gcp", gatherGCP) - } - - wg.Add(1) - go func() { - defer wg.Done() - uDat.FullHostname = getFQDN(uDat.Addresses) - }() - - // Do non-network gathering sequentially since it is fast. - - if id, err := sysinfo.BootID(); err != nil { - if err != sysinfo.ErrFeatureUnsupported { - warnGatherError("bootid", err) - } - } else { - uDat.BootID = id - } - - if config.DetectKubernetes { - gatherKubernetes(uDat.Vendors, os.Getenv) - } - - if config.DetectDocker { - if id, err := sysinfo.DockerID(); err != nil { - if err != sysinfo.ErrFeatureUnsupported && - err != sysinfo.ErrDockerNotFound { - warnGatherError("docker", err) - } - } else { - uDat.Vendors.Docker = &docker{ID: id} - } - } - - if hostname, err := sysinfo.Hostname(); nil == err { - uDat.Hostname = hostname - } else { - warnGatherError("hostname", err) - } - - if bts, err := sysinfo.PhysicalMemoryBytes(); nil == err { - mib := sysinfo.BytesToMebibytes(bts) - uDat.RAMMiB = &mib - } else { - warnGatherError("memory", err) - } - - // Now we wait for everything! - wg.Wait() - - // Override whatever needs to be overridden. - uDat.Config = overrideFromConfig(config) - - if uDat.Vendors.isEmpty() { - // Per spec, we MUST NOT send any vendors hash if it's empty. - uDat.Vendors = nil - } - - return uDat -} - -func gatherKubernetes(v *vendors, getenv func(string) string) { - if host := getenv("KUBERNETES_SERVICE_HOST"); host != "" { - v.Kubernetes = &kubernetes{Host: host} - } -} diff --git a/internal/utilization/utilization_test.go b/internal/utilization/utilization_test.go deleted file mode 100644 index 098ecb62c..000000000 --- a/internal/utilization/utilization_test.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package utilization - -import ( - "bytes" - "encoding/json" - "errors" - "net/http" - "testing" - - "github.com/newrelic/go-agent/internal/crossagent" - "github.com/newrelic/go-agent/internal/logger" -) - -func TestJSONMarshalling(t *testing.T) { - ramInitializer := new(uint64) - *ramInitializer = 1024 - actualProcessors := 4 - configProcessors := 16 - u := Data{ - MetadataVersion: metadataVersion, - LogicalProcessors: &actualProcessors, - RAMMiB: ramInitializer, - Hostname: "localhost", - Vendors: &vendors{ - AWS: &aws{ - InstanceID: "8BADFOOD", - InstanceType: "t2.micro", - AvailabilityZone: "us-west-1", - }, - Docker: &docker{ID: "47cbd16b77c50cbf71401"}, - Kubernetes: &kubernetes{Host: "10.96.0.1"}, - }, - Config: &override{ - LogicalProcessors: &configProcessors, - }, - } - - expect := `{ - "metadata_version": 5, - "logical_processors": 4, - "total_ram_mib": 1024, - "hostname": "localhost", - "config": { - "logical_processors": 16 - }, - "vendors": { - "aws": { - "instanceId": "8BADFOOD", - "instanceType": "t2.micro", - "availabilityZone": "us-west-1" - }, - "docker": { - "id": "47cbd16b77c50cbf71401" - }, - "kubernetes": { - "kubernetes_service_host": "10.96.0.1" - } - } -}` - - j, err := json.MarshalIndent(u, "", "\t") - if err != nil { - t.Error(err) - } - if string(j) != expect { - t.Errorf("strings don't match; \nexpected: %s\n actual: %s\n", expect, string(j)) - } - - // Test that we marshal not-present values to nil. - u.RAMMiB = nil - u.Hostname = "" - u.Config = nil - expect = `{ - "metadata_version": 5, - "logical_processors": 4, - "total_ram_mib": null, - "hostname": "", - "vendors": { - "aws": { - "instanceId": "8BADFOOD", - "instanceType": "t2.micro", - "availabilityZone": "us-west-1" - }, - "docker": { - "id": "47cbd16b77c50cbf71401" - }, - "kubernetes": { - "kubernetes_service_host": "10.96.0.1" - } - } -}` - - j, err = json.MarshalIndent(u, "", "\t") - if err != nil { - t.Error(err) - } - if string(j) != expect { - t.Errorf("strings don't match; \nexpected: %s\n actual: %s\n", expect, string(j)) - } - -} - -type errorRoundTripper struct{ error } - -func (e errorRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { return nil, e } - -// Smoke test the Gather method. -func TestUtilizationHash(t *testing.T) { - config := Config{ - DetectAWS: true, - DetectAzure: true, - DetectDocker: true, - } - client := &http.Client{ - Transport: errorRoundTripper{errors.New("timed out")}, - } - data := gatherWithClient(config, logger.ShimLogger{}, client) - if data.MetadataVersion == 0 || - nil == data.LogicalProcessors || - 0 == *data.LogicalProcessors || - data.RAMMiB == nil || - *data.RAMMiB == 0 || - data.Hostname == "" { - t.Errorf("utilization data unexpected fields: %+v", data) - } -} - -func TestOverrideFromConfig(t *testing.T) { - testcases := []struct { - config Config - expect string - }{ - {Config{}, `null`}, - {Config{LogicalProcessors: 16}, `{"logical_processors":16}`}, - {Config{TotalRAMMIB: 1024}, `{"total_ram_mib":1024}`}, - {Config{BillingHostname: "localhost"}, `{"hostname":"localhost"}`}, - {Config{ - LogicalProcessors: 16, - TotalRAMMIB: 1024, - BillingHostname: "localhost", - }, `{"logical_processors":16,"total_ram_mib":1024,"hostname":"localhost"}`}, - } - - for _, tc := range testcases { - ov := overrideFromConfig(tc.config) - js, err := json.Marshal(ov) - if nil != err { - t.Error(tc.expect, err) - continue - } - if string(js) != tc.expect { - t.Error(tc.expect, string(js)) - } - } -} - -type utilizationCrossAgentTestcase struct { - Name string `json:"testname"` - RAMMIB *uint64 `json:"input_total_ram_mib"` - LogicalProcessors *int `json:"input_logical_processors"` - Hostname string `json:"input_hostname"` - FullHostname string `json:"input_full_hostname"` - Addresses []string `json:"input_ip_address"` - BootID string `json:"input_boot_id"` - AWSID string `json:"input_aws_id"` - AWSType string `json:"input_aws_type"` - AWSZone string `json:"input_aws_zone"` - AzureLocation string `json:"input_azure_location"` - AzureName string `json:"input_azure_name"` - AzureID string `json:"input_azure_id"` - AzureSize string `json:"input_azure_size"` - GCPID json.Number `json:"input_gcp_id"` - GCPType string `json:"input_gcp_type"` - GCPName string `json:"input_gcp_name"` - GCPZone string `json:"input_gcp_zone"` - PCFGUID string `json:"input_pcf_guid"` - PCFIP string `json:"input_pcf_ip"` - PCFMemLimit string `json:"input_pcf_mem_limit"` - ExpectedOutput json.RawMessage `json:"expected_output_json"` - Config struct { - LogicalProcessors json.RawMessage `json:"NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS"` - RAWMMIB json.RawMessage `json:"NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB"` - Hostname string `json:"NEW_RELIC_UTILIZATION_BILLING_HOSTNAME"` - KubernetesHost string `json:"KUBERNETES_SERVICE_HOST"` - } `json:"input_environment_variables"` -} - -func crossAgentVendors(tc utilizationCrossAgentTestcase) *vendors { - v := &vendors{} - - if tc.AWSID != "" && tc.AWSType != "" && tc.AWSZone != "" { - v.AWS = &aws{ - InstanceID: tc.AWSID, - InstanceType: tc.AWSType, - AvailabilityZone: tc.AWSZone, - } - v.AWS.validate() - } - - if tc.AzureLocation != "" && tc.AzureName != "" && tc.AzureID != "" && tc.AzureSize != "" { - v.Azure = &azure{ - Location: tc.AzureLocation, - Name: tc.AzureName, - VMID: tc.AzureID, - VMSize: tc.AzureSize, - } - v.Azure.validate() - } - - if tc.GCPID.String() != "" && tc.GCPType != "" && tc.GCPName != "" && tc.GCPZone != "" { - v.GCP = &gcp{ - ID: numericString(tc.GCPID.String()), - MachineType: tc.GCPType, - Name: tc.GCPName, - Zone: tc.GCPZone, - } - v.GCP.validate() - } - - if tc.PCFIP != "" && tc.PCFGUID != "" && tc.PCFMemLimit != "" { - v.PCF = &pcf{ - InstanceGUID: tc.PCFGUID, - InstanceIP: tc.PCFIP, - MemoryLimit: tc.PCFMemLimit, - } - v.PCF.validate() - } - - gatherKubernetes(v, func(key string) string { - if key == "KUBERNETES_SERVICE_HOST" { - return tc.Config.KubernetesHost - } - return "" - }) - - if v.isEmpty() { - return nil - } - return v -} - -func compactJSON(js []byte) []byte { - buf := new(bytes.Buffer) - if err := json.Compact(buf, js); err != nil { - return nil - } - return buf.Bytes() -} - -func runUtilizationCrossAgentTestcase(t *testing.T, tc utilizationCrossAgentTestcase) { - var ConfigRAWMMIB int - if nil != tc.Config.RAWMMIB { - json.Unmarshal(tc.Config.RAWMMIB, &ConfigRAWMMIB) - } - var ConfigLogicalProcessors int - if nil != tc.Config.LogicalProcessors { - json.Unmarshal(tc.Config.LogicalProcessors, &ConfigLogicalProcessors) - } - - cfg := Config{ - LogicalProcessors: ConfigLogicalProcessors, - TotalRAMMIB: ConfigRAWMMIB, - BillingHostname: tc.Config.Hostname, - } - - data := &Data{ - MetadataVersion: metadataVersion, - LogicalProcessors: tc.LogicalProcessors, - RAMMiB: tc.RAMMIB, - Hostname: tc.Hostname, - BootID: tc.BootID, - Vendors: crossAgentVendors(tc), - Config: overrideFromConfig(cfg), - FullHostname: tc.FullHostname, - Addresses: tc.Addresses, - } - - js, err := json.Marshal(data) - if nil != err { - t.Error(tc.Name, err) - } - - expect := string(compactJSON(tc.ExpectedOutput)) - if string(js) != expect { - t.Error(tc.Name, string(js), expect) - } -} - -func TestUtilizationCrossAgent(t *testing.T) { - var tcs []utilizationCrossAgentTestcase - - input, err := crossagent.ReadFile(`utilization/utilization_json.json`) - if nil != err { - t.Fatal(err) - } - - err = json.Unmarshal(input, &tcs) - if nil != err { - t.Fatal(err) - } - for _, tc := range tcs { - runUtilizationCrossAgentTestcase(t, tc) - } -} - -func TestVendorsIsEmpty(t *testing.T) { - v := &vendors{} - - if !v.isEmpty() { - t.Fatal("default vendors does not register as empty") - } - - v.AWS = &aws{} - v.Azure = &azure{} - v.PCF = &pcf{} - v.GCP = &gcp{} - if v.isEmpty() { - t.Fatal("non-empty vendors registers as empty") - } - - var nilVendors *vendors - if !nilVendors.isEmpty() { - t.Fatal("nil vendors should be empty") - } -} diff --git a/internal_app.go b/internal_app.go deleted file mode 100644 index 1e9bcc2d5..000000000 --- a/internal_app.go +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "errors" - "fmt" - "io" - "math" - "net/http" - "os" - "strings" - "sync" - "time" - - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/logger" -) - -type dataConsumer interface { - Consume(internal.AgentRunID, internal.Harvestable) -} - -type appData struct { - id internal.AgentRunID - data internal.Harvestable -} - -type app struct { - Logger - config Config - rpmControls internal.RpmControls - testHarvest *internal.Harvest - - // placeholderRun is used when the application is not connected. - placeholderRun *appRun - - // initiateShutdown is used to tell the processor to shutdown. - initiateShutdown chan struct{} - - // shutdownStarted and shutdownComplete are closed by the processor - // goroutine to indicate the shutdown status. Two channels are used so - // that the call of app.Shutdown() can block until shutdown has - // completed but other goroutines can exit when shutdown has started. - // This is not just an optimization: This prevents a deadlock if - // harvesting data during the shutdown fails and an attempt is made to - // merge the data into the next harvest. - shutdownStarted chan struct{} - shutdownComplete chan struct{} - - // Sends to these channels should not occur without a <-shutdownStarted - // select option to prevent deadlock. - dataChan chan appData - collectorErrorChan chan internal.RPMResponse - connectChan chan *appRun - - // This mutex protects both `run` and `err`, both of which should only - // be accessed using getState and setState. - sync.RWMutex - // run is non-nil when the app is successfully connected. It is - // immutable. - run *appRun - // err is non-nil if the application will never be connected again - // (disconnect, license exception, shutdown). - err error - - serverless *internal.ServerlessHarvest -} - -func (app *app) doHarvest(h *internal.Harvest, harvestStart time.Time, run *appRun) { - h.CreateFinalMetrics(run.Reply, run) - - payloads := h.Payloads(app.config.DistributedTracer.Enabled) - for _, p := range payloads { - cmd := p.EndpointMethod() - data, err := p.Data(run.Reply.RunID.String(), harvestStart) - - if nil != err { - app.Warn("unable to create harvest data", map[string]interface{}{ - "cmd": cmd, - "error": err.Error(), - }) - continue - } - if nil == data { - continue - } - - call := internal.RpmCmd{ - Collector: run.Reply.Collector, - RunID: run.Reply.RunID.String(), - Name: cmd, - Data: data, - RequestHeadersMap: run.Reply.RequestHeadersMap, - MaxPayloadSize: run.Reply.MaxPayloadSizeInBytes, - } - - resp := internal.CollectorRequest(call, app.rpmControls) - - if resp.IsDisconnect() || resp.IsRestartException() { - select { - case app.collectorErrorChan <- resp: - case <-app.shutdownStarted: - } - return - } - - if nil != resp.Err { - app.Warn("harvest failure", map[string]interface{}{ - "cmd": cmd, - "error": resp.Err.Error(), - "retain_data": resp.ShouldSaveHarvestData(), - }) - } - - if resp.ShouldSaveHarvestData() { - app.Consume(run.Reply.RunID, p) - } - } -} - -func (app *app) connectRoutine() { - connectAttempt := 0 - for { - reply, resp := internal.ConnectAttempt(config{app.config}, - app.config.SecurityPoliciesToken, app.config.HighSecurity, app.rpmControls) - - if reply != nil { - select { - case app.connectChan <- newAppRun(app.config, reply): - case <-app.shutdownStarted: - } - return - } - - if resp.IsDisconnect() { - select { - case app.collectorErrorChan <- resp: - case <-app.shutdownStarted: - } - return - } - - if nil != resp.Err { - app.Warn("application connect failure", map[string]interface{}{ - "error": resp.Err.Error(), - }) - } - - backoff := getConnectBackoffTime(connectAttempt) - time.Sleep(time.Duration(backoff) * time.Second) - connectAttempt++ - } -} - -// Connect backoff time follows the sequence defined at -// https://source.datanerd.us/agents/agent-specs/blob/master/Collector-Response-Handling.md#retries-and-backoffs -func getConnectBackoffTime(attempt int) int { - connectBackoffTimes := [...]int{15, 15, 30, 60, 120, 300} - l := len(connectBackoffTimes) - if (attempt < 0) || (attempt >= l) { - return connectBackoffTimes[l-1] - } - return connectBackoffTimes[attempt] -} - -func processConnectMessages(run *appRun, lg Logger) { - for _, msg := range run.Reply.Messages { - event := "collector message" - cn := map[string]interface{}{"msg": msg.Message} - - switch strings.ToLower(msg.Level) { - case "error": - lg.Error(event, cn) - case "warn": - lg.Warn(event, cn) - case "info": - lg.Info(event, cn) - case "debug", "verbose": - lg.Debug(event, cn) - } - } -} - -func (app *app) process() { - // Both the harvest and the run are non-nil when the app is connected, - // and nil otherwise. - var h *internal.Harvest - var run *appRun - - harvestTicker := time.NewTicker(time.Second) - defer harvestTicker.Stop() - - for { - select { - case <-harvestTicker.C: - if nil != run { - now := time.Now() - if ready := h.Ready(now); nil != ready { - go app.doHarvest(ready, now, run) - } - } - case d := <-app.dataChan: - if nil != run && run.Reply.RunID == d.id { - d.data.MergeIntoHarvest(h) - } - case <-app.initiateShutdown: - close(app.shutdownStarted) - - // Remove the run before merging any final data to - // ensure a bounded number of receives from dataChan. - app.setState(nil, errors.New("application shut down")) - - if nil != run { - for done := false; !done; { - select { - case d := <-app.dataChan: - if run.Reply.RunID == d.id { - d.data.MergeIntoHarvest(h) - } - default: - done = true - } - } - app.doHarvest(h, time.Now(), run) - } - - close(app.shutdownComplete) - return - case resp := <-app.collectorErrorChan: - run = nil - h = nil - app.setState(nil, nil) - - if resp.IsDisconnect() { - app.setState(nil, resp.Err) - app.Error("application disconnected", map[string]interface{}{ - "app": app.config.AppName, - }) - } else if resp.IsRestartException() { - app.Info("application restarted", map[string]interface{}{ - "app": app.config.AppName, - }) - go app.connectRoutine() - } - case run = <-app.connectChan: - h = internal.NewHarvest(time.Now(), run) - app.setState(run, nil) - - app.Info("application connected", map[string]interface{}{ - "app": app.config.AppName, - "run": run.Reply.RunID.String(), - }) - processConnectMessages(run, app) - } - } -} - -func (app *app) Shutdown(timeout time.Duration) { - if !app.config.Enabled { - return - } - if app.config.ServerlessMode.Enabled { - return - } - - select { - case app.initiateShutdown <- struct{}{}: - default: - } - - // Block until shutdown is done or timeout occurs. - t := time.NewTimer(timeout) - select { - case <-app.shutdownComplete: - case <-t.C: - } - t.Stop() - - app.Info("application shutdown", map[string]interface{}{ - "app": app.config.AppName, - }) -} - -func runSampler(app *app, period time.Duration) { - previous := internal.GetSample(time.Now(), app) - t := time.NewTicker(period) - for { - select { - case now := <-t.C: - current := internal.GetSample(now, app) - run, _ := app.getState() - app.Consume(run.Reply.RunID, internal.GetStats(internal.Samples{ - Previous: previous, - Current: current, - })) - previous = current - case <-app.shutdownStarted: - t.Stop() - return - } - } -} - -func (app *app) WaitForConnection(timeout time.Duration) error { - if !app.config.Enabled { - return nil - } - if app.config.ServerlessMode.Enabled { - return nil - } - deadline := time.Now().Add(timeout) - pollPeriod := 50 * time.Millisecond - - for { - run, err := app.getState() - if nil != err { - return err - } - if run.Reply.RunID != "" { - return nil - } - if time.Now().After(deadline) { - return fmt.Errorf("timeout out after %s", timeout.String()) - } - time.Sleep(pollPeriod) - } -} - -func newApp(c Config) (Application, error) { - c = copyConfigReferenceFields(c) - if err := c.Validate(); nil != err { - return nil, err - } - if nil == c.Logger { - c.Logger = logger.ShimLogger{} - } - app := &app{ - Logger: c.Logger, - config: c, - placeholderRun: newAppRun(c, internal.ConnectReplyDefaults()), - - // This channel must be buffered since Shutdown makes a - // non-blocking send attempt. - initiateShutdown: make(chan struct{}, 1), - - shutdownStarted: make(chan struct{}), - shutdownComplete: make(chan struct{}), - connectChan: make(chan *appRun, 1), - collectorErrorChan: make(chan internal.RPMResponse, 1), - dataChan: make(chan appData, internal.AppDataChanSize), - rpmControls: internal.RpmControls{ - License: c.License, - Client: &http.Client{ - Transport: c.Transport, - Timeout: internal.CollectorTimeout, - }, - Logger: c.Logger, - AgentVersion: Version, - }, - } - - app.Info("application created", map[string]interface{}{ - "app": app.config.AppName, - "version": Version, - "enabled": app.config.Enabled, - }) - - if app.config.Enabled { - if app.config.ServerlessMode.Enabled { - reply := newServerlessConnectReply(c) - app.run = newAppRun(c, reply) - app.serverless = internal.NewServerlessHarvest(c.Logger, Version, os.Getenv) - } else { - go app.process() - go app.connectRoutine() - if app.config.RuntimeSampler.Enabled { - go runSampler(app, internal.RuntimeSamplerPeriod) - } - } - } - - return app, nil -} - -var ( - _ internal.HarvestTestinger = &app{} - _ internal.Expect = &app{} -) - -func (app *app) HarvestTesting(replyfn func(*internal.ConnectReply)) { - if nil != replyfn { - reply := internal.ConnectReplyDefaults() - replyfn(reply) - app.placeholderRun = newAppRun(app.config, reply) - } - app.testHarvest = internal.NewHarvest(time.Now(), &internal.DfltHarvestCfgr{}) -} - -func (app *app) getState() (*appRun, error) { - app.RLock() - defer app.RUnlock() - - run := app.run - if nil == run { - run = app.placeholderRun - } - return run, app.err -} - -func (app *app) setState(run *appRun, err error) { - app.Lock() - defer app.Unlock() - - app.run = run - app.err = err -} - -// StartTransaction implements newrelic.Application's StartTransaction. -func (app *app) StartTransaction(name string, w http.ResponseWriter, r *http.Request) Transaction { - run, _ := app.getState() - txn := upgradeTxn(newTxn(txnInput{ - app: app, - appRun: run, - writer: w, - Consumer: app, - }, name)) - - if nil != r { - txn.SetWebRequest(NewWebRequest(r)) - } - return txn -} - -var ( - errHighSecurityEnabled = errors.New("high security enabled") - errCustomEventsDisabled = errors.New("custom events disabled") - errCustomEventsRemoteDisabled = errors.New("custom events disabled by server") -) - -// RecordCustomEvent implements newrelic.Application's RecordCustomEvent. -func (app *app) RecordCustomEvent(eventType string, params map[string]interface{}) error { - if app.config.HighSecurity { - return errHighSecurityEnabled - } - - if !app.config.CustomInsightsEvents.Enabled { - return errCustomEventsDisabled - } - - event, e := internal.CreateCustomEvent(eventType, params, time.Now()) - if nil != e { - return e - } - - run, _ := app.getState() - if !run.Reply.CollectCustomEvents { - return errCustomEventsRemoteDisabled - } - - if !run.Reply.SecurityPolicies.CustomEvents.Enabled() { - return errSecurityPolicy - } - - app.Consume(run.Reply.RunID, event) - - return nil -} - -var ( - errMetricInf = errors.New("invalid metric value: inf") - errMetricNaN = errors.New("invalid metric value: NaN") - errMetricNameEmpty = errors.New("missing metric name") - errMetricServerless = errors.New("custom metrics are not currently supported in serverless mode") -) - -// RecordCustomMetric implements newrelic.Application's RecordCustomMetric. -func (app *app) RecordCustomMetric(name string, value float64) error { - if app.config.ServerlessMode.Enabled { - return errMetricServerless - } - if math.IsNaN(value) { - return errMetricNaN - } - if math.IsInf(value, 0) { - return errMetricInf - } - if "" == name { - return errMetricNameEmpty - } - run, _ := app.getState() - app.Consume(run.Reply.RunID, internal.CustomMetric{ - RawInputName: name, - Value: value, - }) - return nil -} - -var ( - _ internal.ServerlessWriter = &app{} -) - -func (app *app) ServerlessWrite(arn string, writer io.Writer) { - app.serverless.Write(arn, writer) -} - -func (app *app) Consume(id internal.AgentRunID, data internal.Harvestable) { - - app.serverless.Consume(data) - - if nil != app.testHarvest { - data.MergeIntoHarvest(app.testHarvest) - return - } - - if "" == id { - return - } - - select { - case app.dataChan <- appData{id, data}: - case <-app.shutdownStarted: - } -} - -func (app *app) ExpectCustomEvents(t internal.Validator, want []internal.WantEvent) { - internal.ExpectCustomEvents(internal.ExtendValidator(t, "custom events"), app.testHarvest.CustomEvents, want) -} - -func (app *app) ExpectErrors(t internal.Validator, want []internal.WantError) { - t = internal.ExtendValidator(t, "traced errors") - internal.ExpectErrors(t, app.testHarvest.ErrorTraces, want) -} - -func (app *app) ExpectErrorEvents(t internal.Validator, want []internal.WantEvent) { - t = internal.ExtendValidator(t, "error events") - internal.ExpectErrorEvents(t, app.testHarvest.ErrorEvents, want) -} - -func (app *app) ExpectSpanEvents(t internal.Validator, want []internal.WantEvent) { - t = internal.ExtendValidator(t, "spans events") - internal.ExpectSpanEvents(t, app.testHarvest.SpanEvents, want) -} - -func (app *app) ExpectTxnEvents(t internal.Validator, want []internal.WantEvent) { - t = internal.ExtendValidator(t, "txn events") - internal.ExpectTxnEvents(t, app.testHarvest.TxnEvents, want) -} - -func (app *app) ExpectMetrics(t internal.Validator, want []internal.WantMetric) { - t = internal.ExtendValidator(t, "metrics") - internal.ExpectMetrics(t, app.testHarvest.Metrics, want) -} - -func (app *app) ExpectMetricsPresent(t internal.Validator, want []internal.WantMetric) { - t = internal.ExtendValidator(t, "metrics") - internal.ExpectMetricsPresent(t, app.testHarvest.Metrics, want) -} - -func (app *app) ExpectTxnMetrics(t internal.Validator, want internal.WantTxn) { - t = internal.ExtendValidator(t, "metrics") - internal.ExpectTxnMetrics(t, app.testHarvest.Metrics, want) -} - -func (app *app) ExpectTxnTraces(t internal.Validator, want []internal.WantTxnTrace) { - t = internal.ExtendValidator(t, "txn traces") - internal.ExpectTxnTraces(t, app.testHarvest.TxnTraces, want) -} - -func (app *app) ExpectSlowQueries(t internal.Validator, want []internal.WantSlowQuery) { - t = internal.ExtendValidator(t, "slow queries") - internal.ExpectSlowQueries(t, app.testHarvest.SlowSQLs, want) -} diff --git a/internal_app_test.go b/internal_app_test.go deleted file mode 100644 index 6f61c21c9..000000000 --- a/internal_app_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "fmt" - "testing" -) - -func TestConnectBackoff(t *testing.T) { - attempts := map[int]int{ - 0: 15, - 2: 30, - 5: 300, - 6: 300, - 100: 300, - -5: 300, - } - - for k, v := range attempts { - if b := getConnectBackoffTime(k); b != v { - t.Error(fmt.Sprintf("Invalid connect backoff for attempt #%d:", k), v) - } - } -} diff --git a/internal_attributes_test.go b/internal_attributes_test.go deleted file mode 100644 index d7e17a6fb..000000000 --- a/internal_attributes_test.go +++ /dev/null @@ -1,650 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "errors" - "net/http" - "net/url" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -func TestAddAttributeHighSecurity(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.HighSecurity = true - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - - if err := txn.AddAttribute(`key`, 1); err != errHighSecurityEnabled { - t.Error(err) - } - txn.End() - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - }, - AgentAttributes: nil, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestAddAttributeSecurityPolicyDisablesParameters(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.SecurityPolicies.CustomParameters.SetEnabled(false) - } - app := testApp(replyfn, nil, t) - txn := app.StartTransaction("hello", nil, nil) - - if err := txn.AddAttribute(`key`, 1); err != errSecurityPolicy { - t.Error(err) - } - txn.End() - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - }, - AgentAttributes: nil, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestAddAttributeSecurityPolicyDisablesInclude(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.SecurityPolicies.AttributesInclude.SetEnabled(false) - } - cfgfn := func(cfg *Config) { - cfg.TransactionEvents.Attributes.Include = append(cfg.TransactionEvents.Attributes.Include, - AttributeRequestUserAgent) - } - val := "dont-include-me-in-txn-events" - app := testApp(replyfn, cfgfn, t) - req := &http.Request{} - req.Header = make(http.Header) - req.Header.Add("User-Agent", val) - txn := app.StartTransaction("hello", nil, req) - txn.NoticeError(errors.New("hello")) - txn.End() - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - AgentAttributes: map[string]interface{}{}, - UserAttributes: map[string]interface{}{}, - }}) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "hello", - Klass: "*errors.errorString", - AgentAttributes: map[string]interface{}{AttributeRequestUserAgent: val}, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestUserAttributeBasics(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - - txn.NoticeError(errors.New("zap")) - - if err := txn.AddAttribute(`int\key`, 1); nil != err { - t.Error(err) - } - if err := txn.AddAttribute(`str\key`, `zip\zap`); nil != err { - t.Error(err) - } - err := txn.AddAttribute("invalid_value", struct{}{}) - if _, ok := err.(internal.ErrInvalidAttributeType); !ok { - t.Error(err) - } - err = txn.AddAttribute("nil_value", nil) - if _, ok := err.(internal.ErrInvalidAttributeType); !ok { - t.Error(err) - } - txn.End() - if err := txn.AddAttribute("already_ended", "zap"); err != errAlreadyEnded { - t.Error(err) - } - - agentAttributes := map[string]interface{}{} - userAttributes := map[string]interface{}{`int\key`: 1, `str\key`: `zip\zap`} - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - }, - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "zap", - Klass: "*errors.errorString", - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "*errors.errorString", - "error.message": "zap", - "transactionName": "OtherTransaction/Go/hello", - }, - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/hello", - NumSegments: 0, - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) -} - -func TestUserAttributeConfiguration(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.TransactionEvents.Attributes.Exclude = []string{"only_errors", "only_txn_traces"} - cfg.ErrorCollector.Attributes.Exclude = []string{"only_txn_events", "only_txn_traces"} - cfg.TransactionTracer.Attributes.Exclude = []string{"only_txn_events", "only_errors"} - cfg.Attributes.Exclude = []string{"completed_excluded"} - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - - txn.NoticeError(errors.New("zap")) - - if err := txn.AddAttribute("only_errors", 1); nil != err { - t.Error(err) - } - if err := txn.AddAttribute("only_txn_events", 2); nil != err { - t.Error(err) - } - if err := txn.AddAttribute("only_txn_traces", 3); nil != err { - t.Error(err) - } - if err := txn.AddAttribute("completed_excluded", 4); nil != err { - t.Error(err) - } - txn.End() - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - }, - AgentAttributes: map[string]interface{}{}, - UserAttributes: map[string]interface{}{"only_txn_events": 2}, - }}) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "zap", - Klass: "*errors.errorString", - AgentAttributes: map[string]interface{}{}, - UserAttributes: map[string]interface{}{"only_errors": 1}, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "*errors.errorString", - "error.message": "zap", - "transactionName": "OtherTransaction/Go/hello", - }, - AgentAttributes: map[string]interface{}{}, - UserAttributes: map[string]interface{}{"only_errors": 1}, - }}) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/hello", - NumSegments: 0, - AgentAttributes: map[string]interface{}{}, - UserAttributes: map[string]interface{}{"only_txn_traces": 3}, - }}) -} - -// Second attributes have priority. -func mergeAttributes(a1, a2 map[string]interface{}) map[string]interface{} { - a := make(map[string]interface{}) - for k, v := range a1 { - a[k] = v - } - for k, v := range a2 { - a[k] = v - } - return a -} - -var ( - // Agent attributes expected in txn events from usualAttributeTestTransaction. - agent1 = map[string]interface{}{ - AttributeHostDisplayName: `my\host\display\name`, - AttributeResponseCode: `404`, - AttributeResponseContentType: `text/plain; charset=us-ascii`, - AttributeResponseContentLength: 345, - AttributeRequestMethod: "GET", - AttributeRequestAccept: "text/plain", - AttributeRequestContentType: "text/html; charset=utf-8", - AttributeRequestContentLength: 753, - AttributeRequestHost: "my_domain.com", - AttributeRequestURI: "/hello", - } - // Agent attributes expected in errors and traces from usualAttributeTestTransaction. - agent2 = mergeAttributes(agent1, map[string]interface{}{ - AttributeRequestUserAgent: "Mozilla/5.0", - AttributeRequestReferer: "http://en.wikipedia.org/zip", - }) - // User attributes expected from usualAttributeTestTransaction. - user1 = map[string]interface{}{ - "myStr": "hello", - } -) - -func agentAttributeTestcase(t testing.TB, cfgfn func(cfg *Config), e AttributeExpect) { - app := testApp(nil, func(cfg *Config) { - cfg.HostDisplayName = `my\host\display\name` - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - if nil != cfgfn { - cfgfn(cfg) - } - }, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, helloRequest) - txn.NoticeError(errors.New("zap")) - - hdr := txn.Header() - hdr.Set("Content-Type", `text/plain; charset=us-ascii`) - hdr.Set("Content-Length", `345`) - - txn.WriteHeader(404) - txn.AddAttribute("myStr", "hello") - - txn.End() - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - AgentAttributes: e.TxnEvent.Agent, - UserAttributes: e.TxnEvent.User, - }}) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "zap", - Klass: "*errors.errorString", - AgentAttributes: e.Error.Agent, - UserAttributes: e.Error.User, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "*errors.errorString", - "error.message": "zap", - "transactionName": "WebTransaction/Go/hello", - }, - AgentAttributes: e.Error.Agent, - UserAttributes: e.Error.User, - }}) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - NumSegments: 0, - AgentAttributes: e.TxnTrace.Agent, - UserAttributes: e.TxnTrace.User, - }}) -} - -type UserAgent struct { - User map[string]interface{} - Agent map[string]interface{} -} - -type AttributeExpect struct { - TxnEvent UserAgent - Error UserAgent - TxnTrace UserAgent -} - -func TestAgentAttributes(t *testing.T) { - agentAttributeTestcase(t, nil, AttributeExpect{ - TxnEvent: UserAgent{ - Agent: agent1, - User: user1}, - Error: UserAgent{ - Agent: agent2, - User: user1}, - }) -} - -func TestAttributesDisabled(t *testing.T) { - agentAttributeTestcase(t, func(cfg *Config) { - cfg.Attributes.Enabled = false - }, AttributeExpect{ - TxnEvent: UserAgent{ - Agent: map[string]interface{}{}, - User: map[string]interface{}{}}, - Error: UserAgent{ - Agent: map[string]interface{}{}, - User: map[string]interface{}{}}, - TxnTrace: UserAgent{ - Agent: map[string]interface{}{}, - User: map[string]interface{}{}}, - }) -} - -func TestDefaultResponseCode(t *testing.T) { - app := testApp(nil, nil, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, &http.Request{}) - txn.Write([]byte("hello")) - txn.End() - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "S", - }, - AgentAttributes: map[string]interface{}{AttributeResponseCode: 200}, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestNoResponseCode(t *testing.T) { - app := testApp(nil, nil, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, &http.Request{}) - txn.End() - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "S", - }, - AgentAttributes: map[string]interface{}{}, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestTxnEventAttributesDisabled(t *testing.T) { - agentAttributeTestcase(t, func(cfg *Config) { - cfg.TransactionEvents.Attributes.Enabled = false - }, AttributeExpect{ - TxnEvent: UserAgent{ - Agent: map[string]interface{}{}, - User: map[string]interface{}{}}, - Error: UserAgent{ - Agent: agent2, - User: user1}, - TxnTrace: UserAgent{ - Agent: agent2, - User: user1}, - }) -} - -func TestErrorAttributesDisabled(t *testing.T) { - agentAttributeTestcase(t, func(cfg *Config) { - cfg.ErrorCollector.Attributes.Enabled = false - }, AttributeExpect{ - TxnEvent: UserAgent{ - Agent: agent1, - User: user1}, - Error: UserAgent{ - Agent: map[string]interface{}{}, - User: map[string]interface{}{}}, - TxnTrace: UserAgent{ - Agent: agent2, - User: user1}, - }) -} - -func TestTxnTraceAttributesDisabled(t *testing.T) { - agentAttributeTestcase(t, func(cfg *Config) { - cfg.TransactionTracer.Attributes.Enabled = false - }, AttributeExpect{ - TxnEvent: UserAgent{ - Agent: agent1, - User: user1}, - Error: UserAgent{ - Agent: agent2, - User: user1}, - TxnTrace: UserAgent{ - Agent: map[string]interface{}{}, - User: map[string]interface{}{}}, - }) -} - -var ( - allAgentAttributeNames = []string{ - AttributeResponseCode, - AttributeRequestMethod, - AttributeRequestAccept, - AttributeRequestContentType, - AttributeRequestContentLength, - AttributeRequestHost, - AttributeRequestURI, - AttributeResponseContentType, - AttributeResponseContentLength, - AttributeHostDisplayName, - AttributeRequestUserAgent, - AttributeRequestReferer, - } -) - -func TestAgentAttributesExcluded(t *testing.T) { - agentAttributeTestcase(t, func(cfg *Config) { - cfg.Attributes.Exclude = allAgentAttributeNames - }, AttributeExpect{ - TxnEvent: UserAgent{ - Agent: map[string]interface{}{}, - User: user1}, - Error: UserAgent{ - Agent: map[string]interface{}{}, - User: user1}, - TxnTrace: UserAgent{ - Agent: map[string]interface{}{}, - User: user1}, - }) -} - -func TestAgentAttributesExcludedFromErrors(t *testing.T) { - agentAttributeTestcase(t, func(cfg *Config) { - cfg.ErrorCollector.Attributes.Exclude = allAgentAttributeNames - }, AttributeExpect{ - TxnEvent: UserAgent{ - Agent: agent1, - User: user1}, - Error: UserAgent{ - Agent: map[string]interface{}{}, - User: user1}, - TxnTrace: UserAgent{ - Agent: agent2, - User: user1}, - }) -} - -func TestAgentAttributesExcludedFromTxnEvents(t *testing.T) { - agentAttributeTestcase(t, func(cfg *Config) { - cfg.TransactionEvents.Attributes.Exclude = allAgentAttributeNames - }, AttributeExpect{ - TxnEvent: UserAgent{ - Agent: map[string]interface{}{}, - User: user1}, - Error: UserAgent{ - Agent: agent2, - User: user1}, - TxnTrace: UserAgent{ - Agent: agent2, - User: user1}, - }) -} - -func TestAgentAttributesExcludedFromTxnTraces(t *testing.T) { - agentAttributeTestcase(t, func(cfg *Config) { - cfg.TransactionTracer.Attributes.Exclude = allAgentAttributeNames - }, AttributeExpect{ - TxnEvent: UserAgent{ - Agent: agent1, - User: user1}, - Error: UserAgent{ - Agent: agent2, - User: user1}, - TxnTrace: UserAgent{ - Agent: map[string]interface{}{}, - User: user1}, - }) -} - -func TestRequestURIPresent(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - u, err := url.Parse("/hello?remove=me") - if nil != err { - t.Error(err) - } - txn.SetWebRequest(customRequest{u: u}) - txn.NoticeError(errors.New("zap")) - txn.End() - - agentAttributes := map[string]interface{}{"request.uri": "/hello"} - userAttributes := map[string]interface{}{} - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "zap", - Klass: "*errors.errorString", - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "*errors.errorString", - "error.message": "zap", - "transactionName": "WebTransaction/Go/hello", - }, - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - NumSegments: 0, - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) -} - -func TestRequestURIExcluded(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.Attributes.Exclude = append(cfg.Attributes.Exclude, AttributeRequestURI) - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - u, err := url.Parse("/hello?remove=me") - if nil != err { - t.Error(err) - } - txn.SetWebRequest(customRequest{u: u}) - txn.NoticeError(errors.New("zap")) - txn.End() - - agentAttributes := map[string]interface{}{} - userAttributes := map[string]interface{}{} - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "zap", - Klass: "*errors.errorString", - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "*errors.errorString", - "error.message": "zap", - "transactionName": "WebTransaction/Go/hello", - }, - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - NumSegments: 0, - AgentAttributes: agentAttributes, - UserAttributes: userAttributes, - }}) -} - -func TestMessageAttributes(t *testing.T) { - // test that adding message attributes as agent attributes filters them, - // but as user attributes does not filter them. - app := testApp(nil, nil, t) - - txn := app.StartTransaction("hello1", nil, nil) - txn.(internal.AddAgentAttributer).AddAgentAttribute(internal.AttributeMessageRoutingKey, "myRoutingKey", nil) - txn.(internal.AddAgentAttributer).AddAgentAttribute(internal.AttributeMessageExchangeType, "myExchangeType", nil) - txn.(internal.AddAgentAttributer).AddAgentAttribute(internal.AttributeMessageCorrelationID, "myCorrelationID", nil) - txn.(internal.AddAgentAttributer).AddAgentAttribute(internal.AttributeMessageQueueName, "myQueueName", nil) - txn.(internal.AddAgentAttributer).AddAgentAttribute(internal.AttributeMessageReplyTo, "myReplyTo", nil) - txn.End() - - txn = app.StartTransaction("hello2", nil, nil) - txn.AddAttribute(AttributeMessageRoutingKey, "myRoutingKey") - txn.AddAttribute(AttributeMessageExchangeType, "myExchangeType") - txn.AddAttribute(AttributeMessageCorrelationID, "myCorrelationID") - txn.AddAttribute(AttributeMessageQueueName, "myQueueName") - txn.AddAttribute(AttributeMessageReplyTo, "myReplyTo") - txn.End() - - app.ExpectTxnEvents(t, []internal.WantEvent{ - { - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "message.queueName": "myQueueName", - "message.routingKey": "myRoutingKey", - }, - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello1", - }, - }, - { - UserAttributes: map[string]interface{}{ - "message.queueName": "myQueueName", - "message.routingKey": "myRoutingKey", - "message.exchangeType": "myExchangeType", - "message.replyTo": "myReplyTo", - "message.correlationId": "myCorrelationID", - }, - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello2", - }, - }, - }) -} diff --git a/internal_benchmark_test.go b/internal_benchmark_test.go deleted file mode 100644 index 99de315b9..000000000 --- a/internal_benchmark_test.go +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "net/http" - "testing" -) - -var ( - sampleLicense = "0123456789012345678901234567890123456789" -) - -// BenchmarkMuxWithoutNewRelic acts as a control against the other mux -// benchmarks. -func BenchmarkMuxWithoutNewRelic(b *testing.B) { - mux := http.NewServeMux() - mux.HandleFunc(helloPath, handler) - - w := newCompatibleResponseRecorder() - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - mux.ServeHTTP(w, helloRequest) - } -} - -// BenchmarkMuxWithNewRelic shows the approximate overhead of instrumenting a -// request. The numbers here are approximate since this is a test app: rather -// than putting the transaction into a channel to be processed by another -// goroutine, the transaction is merged directly into a harvest. -func BenchmarkMuxWithNewRelic(b *testing.B) { - app := testApp(nil, nil, b) - mux := http.NewServeMux() - mux.HandleFunc(WrapHandleFunc(app, helloPath, handler)) - - w := newCompatibleResponseRecorder() - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - mux.ServeHTTP(w, helloRequest) - } -} - -// BenchmarkMuxWithNewRelic shows the overhead of instrumenting a request when -// the agent is disabled. -func BenchmarkMuxDisabledMode(b *testing.B) { - cfg := NewConfig("my app", sampleLicense) - cfg.Enabled = false - app, err := newApp(cfg) - if nil != err { - b.Fatal(err) - } - mux := http.NewServeMux() - mux.HandleFunc(WrapHandleFunc(app, helloPath, handler)) - - w := newCompatibleResponseRecorder() - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - mux.ServeHTTP(w, helloRequest) - } -} - -// BenchmarkTraceSegmentWithDefer shows the overhead of instrumenting a segment -// using defer. This and BenchmarkTraceSegmentNoDefer are extremely important: -// Timing functions and blocks of code should have minimal cost. -func BenchmarkTraceSegmentWithDefer(b *testing.B) { - cfg := NewConfig("my app", sampleLicense) - cfg.Enabled = false - app, err := newApp(cfg) - if nil != err { - b.Fatal(err) - } - txn := app.StartTransaction("my txn", nil, nil) - fn := func() { - defer StartSegment(txn, "alpha").End() - } - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - fn() - } -} - -func BenchmarkTraceSegmentNoDefer(b *testing.B) { - cfg := NewConfig("my app", sampleLicense) - cfg.Enabled = false - app, err := newApp(cfg) - if nil != err { - b.Fatal(err) - } - txn := app.StartTransaction("my txn", nil, nil) - fn := func() { - s := StartSegment(txn, "alpha") - s.End() - } - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - fn() - } -} - -func BenchmarkTraceSegmentZeroSegmentThreshold(b *testing.B) { - cfg := NewConfig("my app", sampleLicense) - cfg.Enabled = false - cfg.TransactionTracer.SegmentThreshold = 0 - app, err := newApp(cfg) - if nil != err { - b.Fatal(err) - } - txn := app.StartTransaction("my txn", nil, nil) - fn := func() { - s := StartSegment(txn, "alpha") - s.End() - } - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - fn() - } -} - -func BenchmarkDatastoreSegment(b *testing.B) { - cfg := NewConfig("my app", sampleLicense) - cfg.Enabled = false - app, err := newApp(cfg) - if nil != err { - b.Fatal(err) - } - txn := app.StartTransaction("my txn", nil, nil) - fn := func(txn Transaction) { - ds := DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: DatastoreMySQL, - Collection: "my_table", - Operation: "Select", - } - defer ds.End() - } - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - fn(txn) - } -} - -func BenchmarkExternalSegment(b *testing.B) { - cfg := NewConfig("my app", sampleLicense) - cfg.Enabled = false - app, err := newApp(cfg) - if nil != err { - b.Fatal(err) - } - txn := app.StartTransaction("my txn", nil, nil) - fn := func(txn Transaction) { - es := &ExternalSegment{ - StartTime: txn.StartSegmentNow(), - URL: "http://example.com/", - } - defer es.End() - } - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - fn(txn) - } -} - -func BenchmarkTxnWithSegment(b *testing.B) { - app := testApp(nil, nil, b) - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - txn := app.StartTransaction("my txn", nil, nil) - StartSegment(txn, "myFunction").End() - txn.End() - } -} - -func BenchmarkTxnWithDatastore(b *testing.B) { - app := testApp(nil, nil, b) - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - txn := app.StartTransaction("my txn", nil, nil) - ds := &DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: DatastoreMySQL, - Collection: "my_table", - Operation: "Select", - } - ds.End() - txn.End() - } -} - -func BenchmarkTxnWithExternal(b *testing.B) { - app := testApp(nil, nil, b) - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - txn := app.StartTransaction("my txn", nil, nil) - es := &ExternalSegment{ - StartTime: txn.StartSegmentNow(), - URL: "http://example.com", - } - es.End() - txn.End() - } -} diff --git a/internal_browser_test.go b/internal_browser_test.go deleted file mode 100644 index dfb87d83d..000000000 --- a/internal_browser_test.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "reflect" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -func browserReplyFields(reply *internal.ConnectReply) { - reply.AgentLoader = "loader" - reply.Beacon = "beacon" - reply.BrowserKey = "key" - reply.AppID = "app" - reply.ErrorBeacon = "error" - reply.JSAgentFile = "agent" -} - -func TestBrowserTimingHeaderSuccess(t *testing.T) { - includeAttributes := func(cfg *Config) { - cfg.BrowserMonitoring.Attributes.Enabled = true - cfg.BrowserMonitoring.Attributes.Include = []string{AttributeResponseCode} - } - app := testApp(browserReplyFields, includeAttributes, t) - txn := app.StartTransaction("hello", nil, nil) - txn.WriteHeader(200) - txn.AddAttribute("zip", "zap") - hdr, err := txn.BrowserTimingHeader() - if nil != err { - t.Fatal(err) - } - - encodingKey := browserEncodingKey(testLicenseKey) - obfuscatedTxnName, _ := internal.Obfuscate([]byte("OtherTransaction/Go/hello"), encodingKey) - obfuscatedAttributes, _ := internal.Obfuscate([]byte(`{"u":{"zip":"zap"},"a":{"httpResponseCode":"200"}}`), encodingKey) - - // This is a cheat: we can't deterministically set this, but DeepEqual - // doesn't have any ability to say "equal everything except these - // fields". - hdr.info.QueueTimeMillis = 12 - hdr.info.ApplicationTimeMillis = 34 - expected := &BrowserTimingHeader{ - agentLoader: "loader", - info: browserInfo{ - Beacon: "beacon", - LicenseKey: "key", - ApplicationID: "app", - TransactionName: obfuscatedTxnName, - QueueTimeMillis: 12, - ApplicationTimeMillis: 34, - ObfuscatedAttributes: obfuscatedAttributes, - ErrorBeacon: "error", - Agent: "agent", - }, - } - if !reflect.DeepEqual(hdr, expected) { - txnName, _ := internal.Deobfuscate(hdr.info.TransactionName, encodingKey) - attr, _ := internal.Deobfuscate(hdr.info.ObfuscatedAttributes, encodingKey) - t.Errorf("header did not match: expected %#v; got %#v txnName=%s attr=%s", - expected, hdr, string(txnName), string(attr)) - } -} - -func TestBrowserTimingHeaderSuccessWithoutAttributes(t *testing.T) { - // Test that attributes do not get put in the browser footer by default - // configuration. - - app := testApp(browserReplyFields, nil, t) - txn := app.StartTransaction("hello", nil, nil) - txn.WriteHeader(200) - txn.AddAttribute("zip", "zap") - hdr, err := txn.BrowserTimingHeader() - if nil != err { - t.Fatal(err) - } - - encodingKey := browserEncodingKey(testLicenseKey) - obfuscatedTxnName, _ := internal.Obfuscate([]byte("OtherTransaction/Go/hello"), encodingKey) - obfuscatedAttributes, _ := internal.Obfuscate([]byte(`{"u":{},"a":{}}`), encodingKey) - - // This is a cheat: we can't deterministically set this, but DeepEqual - // doesn't have any ability to say "equal everything except these - // fields". - hdr.info.QueueTimeMillis = 12 - hdr.info.ApplicationTimeMillis = 34 - expected := &BrowserTimingHeader{ - agentLoader: "loader", - info: browserInfo{ - Beacon: "beacon", - LicenseKey: "key", - ApplicationID: "app", - TransactionName: obfuscatedTxnName, - QueueTimeMillis: 12, - ApplicationTimeMillis: 34, - ObfuscatedAttributes: obfuscatedAttributes, - ErrorBeacon: "error", - Agent: "agent", - }, - } - if !reflect.DeepEqual(hdr, expected) { - txnName, _ := internal.Deobfuscate(hdr.info.TransactionName, encodingKey) - attr, _ := internal.Deobfuscate(hdr.info.ObfuscatedAttributes, encodingKey) - t.Errorf("header did not match: expected %#v; got %#v txnName=%s attr=%s", - expected, hdr, string(txnName), string(attr)) - } -} - -func TestBrowserTimingHeaderDisabled(t *testing.T) { - disableBrowser := func(cfg *Config) { - cfg.BrowserMonitoring.Enabled = false - } - app := testApp(browserReplyFields, disableBrowser, t) - txn := app.StartTransaction("hello", nil, nil) - hdr, err := txn.BrowserTimingHeader() - if err != errBrowserDisabled { - t.Error(err) - } - if hdr.WithTags() != nil { - t.Error(hdr.WithTags()) - } -} - -func TestBrowserTimingHeaderNotConnected(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - hdr, err := txn.BrowserTimingHeader() - if err != nil { - // No error expected if the app is not yet connected. - t.Error(err) - } - if hdr.WithTags() != nil { - t.Error(hdr.WithTags()) - } -} - -func TestBrowserTimingHeaderAlreadyFinished(t *testing.T) { - app := testApp(browserReplyFields, nil, t) - txn := app.StartTransaction("hello", nil, nil) - txn.End() - hdr, err := txn.BrowserTimingHeader() - if err != errAlreadyEnded { - t.Error(err) - } - if hdr.WithTags() != nil { - t.Error(hdr.WithTags()) - } -} - -func TestBrowserTimingHeaderTxnIgnored(t *testing.T) { - app := testApp(browserReplyFields, nil, t) - txn := app.StartTransaction("hello", nil, nil) - txn.Ignore() - hdr, err := txn.BrowserTimingHeader() - if err != errTransactionIgnored { - t.Error(err) - } - if hdr.WithTags() != nil { - t.Error(hdr.WithTags()) - } -} - -func BenchmarkBrowserTimingHeaderSuccess(b *testing.B) { - app := testApp(browserReplyFields, nil, b) - txn := app.StartTransaction("my txn", nil, nil) - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - hdr, err := txn.BrowserTimingHeader() - if nil == hdr || nil != err { - b.Fatal(hdr, err) - } - hdr.WithTags() - } -} diff --git a/internal_config.go b/internal_config.go deleted file mode 100644 index 6841605ce..000000000 --- a/internal_config.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/json" - "fmt" - "net/http" - "os" - "strings" - - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/logger" - "github.com/newrelic/go-agent/internal/utilization" -) - -func copyDestConfig(c AttributeDestinationConfig) AttributeDestinationConfig { - cp := c - if nil != c.Include { - cp.Include = make([]string, len(c.Include)) - copy(cp.Include, c.Include) - } - if nil != c.Exclude { - cp.Exclude = make([]string, len(c.Exclude)) - copy(cp.Exclude, c.Exclude) - } - return cp -} - -func copyConfigReferenceFields(cfg Config) Config { - cp := cfg - if nil != cfg.Labels { - cp.Labels = make(map[string]string, len(cfg.Labels)) - for key, val := range cfg.Labels { - cp.Labels[key] = val - } - } - if nil != cfg.ErrorCollector.IgnoreStatusCodes { - ignored := make([]int, len(cfg.ErrorCollector.IgnoreStatusCodes)) - copy(ignored, cfg.ErrorCollector.IgnoreStatusCodes) - cp.ErrorCollector.IgnoreStatusCodes = ignored - } - - cp.Attributes = copyDestConfig(cfg.Attributes) - cp.ErrorCollector.Attributes = copyDestConfig(cfg.ErrorCollector.Attributes) - cp.TransactionEvents.Attributes = copyDestConfig(cfg.TransactionEvents.Attributes) - cp.TransactionTracer.Attributes = copyDestConfig(cfg.TransactionTracer.Attributes) - cp.BrowserMonitoring.Attributes = copyDestConfig(cfg.BrowserMonitoring.Attributes) - cp.SpanEvents.Attributes = copyDestConfig(cfg.SpanEvents.Attributes) - cp.TransactionTracer.Segments.Attributes = copyDestConfig(cfg.TransactionTracer.Segments.Attributes) - - return cp -} - -func transportSetting(t http.RoundTripper) interface{} { - if nil == t { - return nil - } - return fmt.Sprintf("%T", t) -} - -func loggerSetting(lg Logger) interface{} { - if nil == lg { - return nil - } - if _, ok := lg.(logger.ShimLogger); ok { - return nil - } - return fmt.Sprintf("%T", lg) -} - -const ( - // https://source.datanerd.us/agents/agent-specs/blob/master/Custom-Host-Names.md - hostByteLimit = 255 -) - -type settings Config - -func (s settings) MarshalJSON() ([]byte, error) { - c := Config(s) - transport := c.Transport - c.Transport = nil - l := c.Logger - c.Logger = nil - - js, err := json.Marshal(c) - if nil != err { - return nil, err - } - fields := make(map[string]interface{}) - err = json.Unmarshal(js, &fields) - if nil != err { - return nil, err - } - // The License field is not simply ignored by adding the `json:"-"` tag - // to it since we want to allow consumers to populate Config from JSON. - delete(fields, `License`) - fields[`Transport`] = transportSetting(transport) - fields[`Logger`] = loggerSetting(l) - - // Browser monitoring support. - if c.BrowserMonitoring.Enabled { - fields[`browser_monitoring.loader`] = "rum" - } - - return json.Marshal(fields) -} - -func configConnectJSONInternal(c Config, pid int, util *utilization.Data, e internal.Environment, version string, securityPolicies *internal.SecurityPolicies, metadata map[string]string) ([]byte, error) { - return json.Marshal([]interface{}{struct { - Pid int `json:"pid"` - Language string `json:"language"` - Version string `json:"agent_version"` - Host string `json:"host"` - HostDisplayName string `json:"display_host,omitempty"` - Settings interface{} `json:"settings"` - AppName []string `json:"app_name"` - HighSecurity bool `json:"high_security"` - Labels internal.Labels `json:"labels,omitempty"` - Environment internal.Environment `json:"environment"` - Identifier string `json:"identifier"` - Util *utilization.Data `json:"utilization"` - SecurityPolicies *internal.SecurityPolicies `json:"security_policies,omitempty"` - Metadata map[string]string `json:"metadata"` - EventData internal.EventHarvestConfig `json:"event_harvest_config"` - }{ - Pid: pid, - Language: internal.AgentLanguage, - Version: version, - Host: internal.StringLengthByteLimit(util.Hostname, hostByteLimit), - HostDisplayName: internal.StringLengthByteLimit(c.HostDisplayName, hostByteLimit), - Settings: (settings)(c), - AppName: strings.Split(c.AppName, ";"), - HighSecurity: c.HighSecurity, - Labels: c.Labels, - Environment: e, - // This identifier field is provided to avoid: - // https://newrelic.atlassian.net/browse/DSCORE-778 - // - // This identifier is used by the collector to look up the real - // agent. If an identifier isn't provided, the collector will - // create its own based on the first appname, which prevents a - // single daemon from connecting "a;b" and "a;c" at the same - // time. - // - // Providing the identifier below works around this issue and - // allows users more flexibility in using application rollups. - Identifier: c.AppName, - Util: util, - SecurityPolicies: securityPolicies, - Metadata: metadata, - EventData: internal.DefaultEventHarvestConfig(c), - }}) -} - -const ( - // https://source.datanerd.us/agents/agent-specs/blob/master/Connect-LEGACY.md#metadata-hash - metadataPrefix = "NEW_RELIC_METADATA_" -) - -func gatherMetadata(environ func() []string) map[string]string { - metadata := make(map[string]string) - env := environ() - for _, pair := range env { - if strings.HasPrefix(pair, metadataPrefix) { - idx := strings.Index(pair, "=") - if idx >= 0 { - metadata[pair[0:idx]] = pair[idx+1:] - } - } - } - return metadata -} - -// config allows CreateConnectJSON to be a method on a non-public type. -type config struct{ Config } - -func (c config) CreateConnectJSON(securityPolicies *internal.SecurityPolicies) ([]byte, error) { - env := internal.NewEnvironment() - util := utilization.Gather(utilization.Config{ - DetectAWS: c.Utilization.DetectAWS, - DetectAzure: c.Utilization.DetectAzure, - DetectPCF: c.Utilization.DetectPCF, - DetectGCP: c.Utilization.DetectGCP, - DetectDocker: c.Utilization.DetectDocker, - DetectKubernetes: c.Utilization.DetectKubernetes, - LogicalProcessors: c.Utilization.LogicalProcessors, - TotalRAMMIB: c.Utilization.TotalRAMMIB, - BillingHostname: c.Utilization.BillingHostname, - }, c.Logger) - return configConnectJSONInternal(c.Config, os.Getpid(), util, env, Version, securityPolicies, gatherMetadata(os.Environ)) -} diff --git a/internal_config_test.go b/internal_config_test.go deleted file mode 100644 index f9cb25771..000000000 --- a/internal_config_test.go +++ /dev/null @@ -1,478 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/json" - "net/http" - "os" - "reflect" - "regexp" - "strconv" - "strings" - "testing" - - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/utilization" -) - -var ( - fixRegex = regexp.MustCompile(`e\+\d+`) -) - -// In Go 1.8 Marshalling of numbers was changed: -// Before: "StackTraceThreshold":5e+08 -// After: "StackTraceThreshold":500000000 -func standardizeNumbers(input string) string { - return fixRegex.ReplaceAllStringFunc(input, func(s string) string { - n, err := strconv.Atoi(s[2:]) - if nil != err { - return s - } - return strings.Repeat("0", n) - }) -} - -func TestCopyConfigReferenceFieldsPresent(t *testing.T) { - cfg := NewConfig("my appname", "0123456789012345678901234567890123456789") - cfg.Labels["zip"] = "zap" - cfg.ErrorCollector.IgnoreStatusCodes = append(cfg.ErrorCollector.IgnoreStatusCodes, 405) - cfg.Attributes.Include = append(cfg.Attributes.Include, "1") - cfg.Attributes.Exclude = append(cfg.Attributes.Exclude, "2") - cfg.TransactionEvents.Attributes.Include = append(cfg.TransactionEvents.Attributes.Include, "3") - cfg.TransactionEvents.Attributes.Exclude = append(cfg.TransactionEvents.Attributes.Exclude, "4") - cfg.ErrorCollector.Attributes.Include = append(cfg.ErrorCollector.Attributes.Include, "5") - cfg.ErrorCollector.Attributes.Exclude = append(cfg.ErrorCollector.Attributes.Exclude, "6") - cfg.TransactionTracer.Attributes.Include = append(cfg.TransactionTracer.Attributes.Include, "7") - cfg.TransactionTracer.Attributes.Exclude = append(cfg.TransactionTracer.Attributes.Exclude, "8") - cfg.BrowserMonitoring.Attributes.Include = append(cfg.BrowserMonitoring.Attributes.Include, "9") - cfg.BrowserMonitoring.Attributes.Exclude = append(cfg.BrowserMonitoring.Attributes.Exclude, "10") - cfg.SpanEvents.Attributes.Include = append(cfg.SpanEvents.Attributes.Include, "11") - cfg.SpanEvents.Attributes.Exclude = append(cfg.SpanEvents.Attributes.Exclude, "12") - cfg.TransactionTracer.Segments.Attributes.Include = append(cfg.TransactionTracer.Segments.Attributes.Include, "13") - cfg.TransactionTracer.Segments.Attributes.Exclude = append(cfg.TransactionTracer.Segments.Attributes.Exclude, "14") - cfg.Transport = &http.Transport{} - cfg.Logger = NewLogger(os.Stdout) - - cp := copyConfigReferenceFields(cfg) - - cfg.Labels["zop"] = "zup" - cfg.ErrorCollector.IgnoreStatusCodes[0] = 201 - cfg.Attributes.Include[0] = "zap" - cfg.Attributes.Exclude[0] = "zap" - cfg.TransactionEvents.Attributes.Include[0] = "zap" - cfg.TransactionEvents.Attributes.Exclude[0] = "zap" - cfg.ErrorCollector.Attributes.Include[0] = "zap" - cfg.ErrorCollector.Attributes.Exclude[0] = "zap" - cfg.TransactionTracer.Attributes.Include[0] = "zap" - cfg.TransactionTracer.Attributes.Exclude[0] = "zap" - cfg.BrowserMonitoring.Attributes.Include[0] = "zap" - cfg.BrowserMonitoring.Attributes.Exclude[0] = "zap" - cfg.SpanEvents.Attributes.Include[0] = "zap" - cfg.SpanEvents.Attributes.Exclude[0] = "zap" - cfg.TransactionTracer.Segments.Attributes.Include[0] = "zap" - cfg.TransactionTracer.Segments.Attributes.Exclude[0] = "zap" - - expect := internal.CompactJSONString(`[ - { - "pid":123, - "language":"go", - "agent_version":"0.2.2", - "host":"my-hostname", - "settings":{ - "AppName":"my appname", - "Attributes":{"Enabled":true,"Exclude":["2"],"Include":["1"]}, - "BrowserMonitoring":{ - "Attributes":{"Enabled":false,"Exclude":["10"],"Include":["9"]}, - "Enabled":true - }, - "CrossApplicationTracer":{"Enabled":true}, - "CustomInsightsEvents":{"Enabled":true}, - "DatastoreTracer":{ - "DatabaseNameReporting":{"Enabled":true}, - "InstanceReporting":{"Enabled":true}, - "QueryParameters":{"Enabled":true}, - "SlowQuery":{ - "Enabled":true, - "Threshold":10000000 - } - }, - "DistributedTracer":{"Enabled":false}, - "Enabled":true, - "ErrorCollector":{ - "Attributes":{"Enabled":true,"Exclude":["6"],"Include":["5"]}, - "CaptureEvents":true, - "Enabled":true, - "IgnoreStatusCodes":[0,5,404,405] - }, - "HighSecurity":false, - "HostDisplayName":"", - "Labels":{"zip":"zap"}, - "Logger":"*logger.logFile", - "RuntimeSampler":{"Enabled":true}, - "SecurityPoliciesToken":"", - "ServerlessMode":{ - "AccountID":"", - "ApdexThreshold":500000000, - "Enabled":false, - "PrimaryAppID":"", - "TrustedAccountKey":"" - }, - "SpanEvents":{ - "Attributes":{ - "Enabled":true,"Exclude":["12"],"Include":["11"] - }, - "Enabled":true - }, - "TransactionEvents":{ - "Attributes":{"Enabled":true,"Exclude":["4"],"Include":["3"]}, - "Enabled":true, - "MaxSamplesStored": 10000 - }, - "TransactionTracer":{ - "Attributes":{"Enabled":true,"Exclude":["8"],"Include":["7"]}, - "Enabled":true, - "SegmentThreshold":2000000, - "Segments":{"Attributes":{"Enabled":true,"Exclude":["14"],"Include":["13"]}}, - "StackTraceThreshold":500000000, - "Threshold":{ - "Duration":500000000, - "IsApdexFailing":true - } - }, - "Transport":"*http.Transport", - "Utilization":{ - "BillingHostname":"", - "DetectAWS":true, - "DetectAzure":true, - "DetectDocker":true, - "DetectGCP":true, - "DetectKubernetes":true, - "DetectPCF":true, - "LogicalProcessors":0, - "TotalRAMMIB":0 - }, - "browser_monitoring.loader":"rum" - }, - "app_name":["my appname"], - "high_security":false, - "labels":[{"label_type":"zip","label_value":"zap"}], - "environment":[ - ["runtime.Compiler","comp"], - ["runtime.GOARCH","arch"], - ["runtime.GOOS","goos"], - ["runtime.Version","vers"], - ["runtime.NumCPU",8] - ], - "identifier":"my appname", - "utilization":{ - "metadata_version":5, - "logical_processors":16, - "total_ram_mib":1024, - "hostname":"my-hostname" - }, - "security_policies":{ - "record_sql":{"enabled":false}, - "attributes_include":{"enabled":false}, - "allow_raw_exception_messages":{"enabled":false}, - "custom_events":{"enabled":false}, - "custom_parameters":{"enabled":false} - }, - "metadata":{ - "NEW_RELIC_METADATA_ZAP":"zip" - }, - "event_harvest_config": { - "report_period_ms": 60000, - "harvest_limits": { - "analytic_event_data": 10000, - "custom_event_data": 10000, - "error_event_data": 100 - } - } - }]`) - - securityPoliciesInput := []byte(`{ - "record_sql": { "enabled": false, "required": false }, - "attributes_include": { "enabled": false, "required": false }, - "allow_raw_exception_messages": { "enabled": false, "required": false }, - "custom_events": { "enabled": false, "required": false }, - "custom_parameters": { "enabled": false, "required": false }, - "custom_instrumentation_editor": { "enabled": false, "required": false }, - "message_parameters": { "enabled": false, "required": false }, - "job_arguments": { "enabled": false, "required": false } - }`) - var sp internal.SecurityPolicies - err := json.Unmarshal(securityPoliciesInput, &sp) - if nil != err { - t.Fatal(err) - } - - metadata := map[string]string{ - "NEW_RELIC_METADATA_ZAP": "zip", - } - js, err := configConnectJSONInternal(cp, 123, &utilization.SampleData, internal.SampleEnvironment, "0.2.2", sp.PointerIfPopulated(), metadata) - if nil != err { - t.Fatal(err) - } - out := standardizeNumbers(string(js)) - if out != expect { - t.Error(out) - } -} - -func TestCopyConfigReferenceFieldsAbsent(t *testing.T) { - cfg := NewConfig("my appname", "0123456789012345678901234567890123456789") - cfg.Labels = nil - cfg.ErrorCollector.IgnoreStatusCodes = nil - - cp := copyConfigReferenceFields(cfg) - - expect := internal.CompactJSONString(`[ - { - "pid":123, - "language":"go", - "agent_version":"0.2.2", - "host":"my-hostname", - "settings":{ - "AppName":"my appname", - "Attributes":{"Enabled":true,"Exclude":null,"Include":null}, - "BrowserMonitoring":{ - "Attributes":{ - "Enabled":false, - "Exclude":null, - "Include":null - }, - "Enabled":true - }, - "CrossApplicationTracer":{"Enabled":true}, - "CustomInsightsEvents":{"Enabled":true}, - "DatastoreTracer":{ - "DatabaseNameReporting":{"Enabled":true}, - "InstanceReporting":{"Enabled":true}, - "QueryParameters":{"Enabled":true}, - "SlowQuery":{ - "Enabled":true, - "Threshold":10000000 - } - }, - "DistributedTracer":{"Enabled":false}, - "Enabled":true, - "ErrorCollector":{ - "Attributes":{"Enabled":true,"Exclude":null,"Include":null}, - "CaptureEvents":true, - "Enabled":true, - "IgnoreStatusCodes":null - }, - "HighSecurity":false, - "HostDisplayName":"", - "Labels":null, - "Logger":null, - "RuntimeSampler":{"Enabled":true}, - "SecurityPoliciesToken":"", - "ServerlessMode":{ - "AccountID":"", - "ApdexThreshold":500000000, - "Enabled":false, - "PrimaryAppID":"", - "TrustedAccountKey":"" - }, - "SpanEvents":{ - "Attributes":{"Enabled":true,"Exclude":null,"Include":null}, - "Enabled":true - }, - "TransactionEvents":{ - "Attributes":{"Enabled":true,"Exclude":null,"Include":null}, - "Enabled":true, - "MaxSamplesStored": 10000 - }, - "TransactionTracer":{ - "Attributes":{"Enabled":true,"Exclude":null,"Include":null}, - "Enabled":true, - "SegmentThreshold":2000000, - "Segments":{"Attributes":{"Enabled":true,"Exclude":null,"Include":null}}, - "StackTraceThreshold":500000000, - "Threshold":{ - "Duration":500000000, - "IsApdexFailing":true - } - }, - "Transport":null, - "Utilization":{ - "BillingHostname":"", - "DetectAWS":true, - "DetectAzure":true, - "DetectDocker":true, - "DetectGCP":true, - "DetectKubernetes":true, - "DetectPCF":true, - "LogicalProcessors":0, - "TotalRAMMIB":0 - }, - "browser_monitoring.loader":"rum" - }, - "app_name":["my appname"], - "high_security":false, - "environment":[ - ["runtime.Compiler","comp"], - ["runtime.GOARCH","arch"], - ["runtime.GOOS","goos"], - ["runtime.Version","vers"], - ["runtime.NumCPU",8] - ], - "identifier":"my appname", - "utilization":{ - "metadata_version":5, - "logical_processors":16, - "total_ram_mib":1024, - "hostname":"my-hostname" - }, - "metadata":{}, - "event_harvest_config": { - "report_period_ms": 60000, - "harvest_limits": { - "analytic_event_data": 10000, - "custom_event_data": 10000, - "error_event_data": 100 - } - } - }]`) - - metadata := map[string]string{} - js, err := configConnectJSONInternal(cp, 123, &utilization.SampleData, internal.SampleEnvironment, "0.2.2", nil, metadata) - if nil != err { - t.Fatal(err) - } - out := standardizeNumbers(string(js)) - if out != expect { - t.Error(string(js)) - } -} - -func TestValidate(t *testing.T) { - c := Config{ - License: "0123456789012345678901234567890123456789", - AppName: "my app", - Enabled: true, - } - if err := c.Validate(); nil != err { - t.Error(err) - } - c = Config{ - License: "", - AppName: "my app", - Enabled: true, - } - if err := c.Validate(); err != errLicenseLen { - t.Error(err) - } - c = Config{ - License: "", - AppName: "my app", - Enabled: false, - } - if err := c.Validate(); nil != err { - t.Error(err) - } - c = Config{ - License: "wronglength", - AppName: "my app", - Enabled: true, - } - if err := c.Validate(); err != errLicenseLen { - t.Error(err) - } - c = Config{ - License: "0123456789012345678901234567890123456789", - AppName: "too;many;app;names", - Enabled: true, - } - if err := c.Validate(); err != errAppNameLimit { - t.Error(err) - } - c = Config{ - License: "0123456789012345678901234567890123456789", - AppName: "", - Enabled: true, - } - if err := c.Validate(); err != errAppNameMissing { - t.Error(err) - } - c = Config{ - License: "0123456789012345678901234567890123456789", - AppName: "", - Enabled: false, - } - if err := c.Validate(); err != nil { - t.Error(err) - } - c = Config{ - License: "0123456789012345678901234567890123456789", - AppName: "my app", - Enabled: true, - HighSecurity: true, - } - if err := c.Validate(); err != nil { - t.Error(err) - } -} - -func TestValidateWithPoliciesToken(t *testing.T) { - c := Config{ - License: "0123456789012345678901234567890123456789", - AppName: "my app", - Enabled: true, - HighSecurity: true, - SecurityPoliciesToken: "0123456789", - } - if err := c.Validate(); err != errHighSecurityWithSecurityPolicies { - t.Error(err) - } - c = Config{ - License: "0123456789012345678901234567890123456789", - AppName: "my app", - Enabled: true, - SecurityPoliciesToken: "0123456789", - } - if err := c.Validate(); err != nil { - t.Error(err) - } -} - -func TestGatherMetadata(t *testing.T) { - metadata := gatherMetadata(func() []string { return nil }) - if !reflect.DeepEqual(metadata, map[string]string{}) { - t.Error(metadata) - } - metadata = gatherMetadata(func() []string { - return []string{ - "NEW_RELIC_METADATA_ZIP=zap", - "NEW_RELIC_METADATA_PIZZA=cheese", - "NEW_RELIC_METADATA_=hello", - "NEW_RELIC_METADATA_LOTS_OF_EQUALS=one=two", - "NEW_RELIC_METADATA_", - "NEW_RELIC_METADATA_NO_EQUALS", - "NEW_RELIC_METADATA_EMPTY=", - "NEW_RELIC_", - "hello=world", - } - }) - if !reflect.DeepEqual(metadata, map[string]string{ - "NEW_RELIC_METADATA_ZIP": "zap", - "NEW_RELIC_METADATA_PIZZA": "cheese", - "NEW_RELIC_METADATA_": "hello", - "NEW_RELIC_METADATA_LOTS_OF_EQUALS": "one=two", - "NEW_RELIC_METADATA_EMPTY": "", - }) { - t.Error(metadata) - } -} - -func TestValidateServerless(t *testing.T) { - // AppName and License can be empty in serverless mode. - c := NewConfig("", "") - c.ServerlessMode.Enabled = true - if err := c.Validate(); nil != err { - t.Error(err) - } -} diff --git a/internal_context_test.go b/internal_context_test.go deleted file mode 100644 index 3502f172b..000000000 --- a/internal_context_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.7 - -package newrelic - -import ( - "net/http" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -func TestWrapHandlerContext(t *testing.T) { - // Test that WrapHandleFunc adds the transaction to the request's - // context, and that it is accessible through FromContext. - - app := testApp(nil, nil, t) - _, h := WrapHandleFunc(app, "myTxn", func(rw http.ResponseWriter, r *http.Request) { - txn := FromContext(r.Context()) - segment := StartSegment(txn, "mySegment") - segment.End() - }) - req, _ := http.NewRequest("GET", "", nil) - h(nil, req) - - scope := "WebTransaction/Go/myTxn" - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/myTxn", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/myTxn", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/myTxn", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/mySegment", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/mySegment", Scope: scope, Forced: false, Data: nil}, - }) -} - -func TestStartExternalSegmentNilTransaction(t *testing.T) { - // Test that StartExternalSegment pulls the transaction from the - // request's context if it is not explicitly provided. - - app := testApp(nil, nil, t) - txn := app.StartTransaction("myTxn", nil, nil) - - req, _ := http.NewRequest("GET", "http://example.com", nil) - req = RequestWithTransactionContext(req, txn) - segment := StartExternalSegment(nil, req) - segment.End() - txn.End() - - scope := "OtherTransaction/Go/myTxn" - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/myTxn", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/myTxn", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/example.com/http/GET", Scope: scope, Forced: false, Data: nil}, - }) -} -func TestNewRoundTripperNilTransaction(t *testing.T) { - // Test that NewRoundTripper pulls the transaction from the - // request's context if it is not explicitly provided. - - app := testApp(nil, nil, t) - txn := app.StartTransaction("myTxn", nil, nil) - - client := &http.Client{} - client.Transport = roundTripperFunc(func(*http.Request) (*http.Response, error) { - return &http.Response{}, nil - }) - client.Transport = NewRoundTripper(nil, client.Transport) - req, _ := http.NewRequest("GET", "http://example.com", nil) - req = RequestWithTransactionContext(req, txn) - client.Do(req) - txn.End() - - scope := "OtherTransaction/Go/myTxn" - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/myTxn", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/myTxn", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/example.com/http/GET", Scope: scope, Forced: false, Data: nil}, - }) -} diff --git a/internal_cross_process_test.go b/internal_cross_process_test.go deleted file mode 100644 index 254b99dcf..000000000 --- a/internal_cross_process_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "errors" - - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/cat" -) - -var ( - crossProcessReplyFn = func(reply *internal.ConnectReply) { - reply.EncodingKey = "encoding_key" - reply.CrossProcessID = "12345#67890" - reply.TrustedAccounts = map[int]struct{}{ - 12345: {}, - } - } - catIntrinsics = map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.pathHash": "fa013f2a", - "nr.guid": internal.MatchAnything, - "nr.referringTransactionGuid": internal.MatchAnything, - "nr.referringPathHash": "41c04f7d", - "nr.apdexPerfZone": "S", - "client_cross_process_id": "12345#67890", - "nr.tripId": internal.MatchAnything, - } -) - -func inboundCrossProcessRequestFactory() *http.Request { - cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true } - app := testApp(crossProcessReplyFn, cfgFn, nil) - clientTxn := app.StartTransaction("client", nil, nil) - req, err := http.NewRequest("GET", "newrelic.com", nil) - StartExternalSegment(clientTxn, req) - if "" == req.Header.Get(cat.NewRelicIDName) { - panic("missing cat header NewRelicIDName: " + req.Header.Get(cat.NewRelicIDName)) - } - if "" == req.Header.Get(cat.NewRelicTxnName) { - panic("missing cat header NewRelicTxnName: " + req.Header.Get(cat.NewRelicTxnName)) - } - if nil != err { - panic(err) - } - return req -} - -func outboundCrossProcessResponse() http.Header { - cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true } - app := testApp(crossProcessReplyFn, cfgFn, nil) - rw := httptest.NewRecorder() - txn := app.StartTransaction("txn", rw, inboundCrossProcessRequestFactory()) - txn.WriteHeader(200) - return rw.HeaderMap -} - -func TestCrossProcessWriteHeaderSuccess(t *testing.T) { - // Test that the CAT response header is present when the consumer uses - // txn.WriteHeader. - cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true } - app := testApp(crossProcessReplyFn, cfgFn, t) - w := httptest.NewRecorder() - txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory()) - txn.WriteHeader(200) - txn.End() - - if "" == w.Header().Get(cat.NewRelicAppDataName) { - t.Error(w.Header().Get(cat.NewRelicAppDataName)) - } - - app.ExpectMetrics(t, webMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: catIntrinsics, - AgentAttributes: map[string]interface{}{ - "request.method": "GET", - "httpResponseCode": 200, - "request.uri": "newrelic.com", - }, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestCrossProcessWriteSuccess(t *testing.T) { - // Test that the CAT response header is present when the consumer uses - // txn.Write. - cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true } - app := testApp(crossProcessReplyFn, cfgFn, t) - w := httptest.NewRecorder() - txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory()) - txn.Write([]byte("response text")) - txn.End() - - if "" == w.Header().Get(cat.NewRelicAppDataName) { - t.Error(w.Header().Get(cat.NewRelicAppDataName)) - } - - app.ExpectMetrics(t, webMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: catIntrinsics, - // Do not test attributes here: In Go 1.5 - // response.headers.contentType will be not be present. - AgentAttributes: nil, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestCATRoundTripper(t *testing.T) { - cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - url := "http://example.com/" - client := &http.Client{} - inner := roundTripperFunc(func(r *http.Request) (*http.Response, error) { - // TODO test that request headers have been set here. - if r.URL.String() != url { - t.Error(r.URL.String()) - } - return nil, errors.New("hello") - }) - client.Transport = NewRoundTripper(txn, inner) - resp, err := client.Get(url) - if resp != nil || err == nil { - t.Error(resp, err.Error()) - } - txn.NoticeError(myError{}) - txn.End() - scope := "OtherTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/example.com/http/GET", Scope: scope, Forced: false, Data: nil}, - }, backgroundErrorMetrics...)) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - "nr.guid": internal.MatchAnything, - "nr.tripId": internal.MatchAnything, - "nr.pathHash": internal.MatchAnything, - }, - }}) -} - -func TestCrossProcessLocallyDisabled(t *testing.T) { - // Test that the CAT can be disabled by local configuration. - cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = false } - app := testApp(crossProcessReplyFn, cfgFn, t) - w := httptest.NewRecorder() - txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory()) - txn.Write([]byte("response text")) - txn.End() - - if "" != w.Header().Get(cat.NewRelicAppDataName) { - t.Error(w.Header().Get(cat.NewRelicAppDataName)) - } - - app.ExpectMetrics(t, webMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "S", - }, - // Do not test attributes here: In Go 1.5 - // response.headers.contentType will be not be present. - AgentAttributes: nil, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestCrossProcessDisabledByServerSideConfig(t *testing.T) { - // Test that the CAT can be disabled by server-side-config. - cfgFn := func(cfg *Config) {} - replyfn := func(reply *internal.ConnectReply) { - crossProcessReplyFn(reply) - json.Unmarshal([]byte(`{"agent_config":{"cross_application_tracer.enabled":false}}`), reply) - } - app := testApp(replyfn, cfgFn, t) - w := httptest.NewRecorder() - txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory()) - txn.Write([]byte("response text")) - txn.End() - - if "" != w.Header().Get(cat.NewRelicAppDataName) { - t.Error(w.Header().Get(cat.NewRelicAppDataName)) - } - - app.ExpectMetrics(t, webMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "S", - }, - // Do not test attributes here: In Go 1.5 - // response.headers.contentType will be not be present. - AgentAttributes: nil, - UserAttributes: map[string]interface{}{}, - }}) -} - -func TestCrossProcessEnabledByServerSideConfig(t *testing.T) { - // Test that the CAT can be enabled by server-side-config. - cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = false } - replyfn := func(reply *internal.ConnectReply) { - crossProcessReplyFn(reply) - json.Unmarshal([]byte(`{"agent_config":{"cross_application_tracer.enabled":true}}`), reply) - } - app := testApp(replyfn, cfgFn, t) - w := httptest.NewRecorder() - txn := app.StartTransaction("hello", w, inboundCrossProcessRequestFactory()) - txn.Write([]byte("response text")) - txn.End() - - if "" == w.Header().Get(cat.NewRelicAppDataName) { - t.Error(w.Header().Get(cat.NewRelicAppDataName)) - } - - app.ExpectMetrics(t, webMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: catIntrinsics, - // Do not test attributes here: In Go 1.5 - // response.headers.contentType will be not be present. - AgentAttributes: nil, - UserAttributes: map[string]interface{}{}, - }}) -} diff --git a/internal_distributed_trace_test.go b/internal_distributed_trace_test.go deleted file mode 100644 index 200cc0313..000000000 --- a/internal_distributed_trace_test.go +++ /dev/null @@ -1,1635 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "net/url" - "reflect" - "strings" - "testing" - "time" - - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/crossagent" -) - -type PayloadTest struct { - V *[2]int `json:"v,omitempty"` - D map[string]interface{} `json:"d,omitempty"` -} - -func distributedTracingReplyFields(reply *internal.ConnectReply) { - reply.AccountID = "123" - reply.AppID = "456" - reply.PrimaryAppID = "456" - reply.TrustedAccounts = map[int]struct{}{ - 123: {}, - } - reply.TrustedAccountKey = "123" - - reply.AdaptiveSampler = internal.SampleEverything{} -} - -func distributedTracingReplyFieldsNeedTrustKey(reply *internal.ConnectReply) { - reply.AccountID = "123" - reply.AppID = "456" - reply.PrimaryAppID = "456" - reply.TrustedAccounts = map[int]struct{}{ - 123: {}, - } - reply.TrustedAccountKey = "789" -} - -func makePayload(app Application, u *url.URL) DistributedTracePayload { - txn := app.StartTransaction("hello", nil, nil) - return txn.CreateDistributedTracePayload() -} - -func enableOldCATDisableBetterCat(cfg *Config) { - cfg.CrossApplicationTracer.Enabled = true - cfg.DistributedTracer.Enabled = false -} - -func disableCAT(cfg *Config) { - cfg.CrossApplicationTracer.Enabled = false - cfg.DistributedTracer.Enabled = false -} - -func enableBetterCAT(cfg *Config) { - cfg.CrossApplicationTracer.Enabled = false - cfg.DistributedTracer.Enabled = true -} - -func disableSpanEvents(cfg *Config) { - cfg.CrossApplicationTracer.Enabled = false - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Enabled = false -} - -func disableDistributedTracerEnableSpanEvents(cfg *Config) { - cfg.CrossApplicationTracer.Enabled = true - cfg.DistributedTracer.Enabled = false - cfg.SpanEvents.Enabled = true -} - -var ( - distributedTracingSuccessMetrics = []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: singleCount}, - } -) - -func TestPayloadConnection(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - payload := makePayload(app, nil) - ip, ok := payload.(internal.Payload) - if !ok { - t.Fatal(payload) - } - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, distributedTracingSuccessMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "parent.type": "App", - "parent.account": "123", - "parent.app": "456", - "parent.transportType": "HTTP", - "parent.transportDuration": internal.MatchAnything, - "parentId": ip.TransactionID, - "traceId": ip.TransactionID, - "parentSpanId": ip.ID, - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - }, - }}) -} - -func TestAcceptMultiple(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - payload := makePayload(app, nil) - ip, ok := payload.(internal.Payload) - if !ok { - t.Fatal(payload) - } - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if nil != err { - t.Error(err) - } - err = txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if err != errAlreadyAccepted { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/AcceptPayload/Ignored/Multiple", Scope: "", Forced: true, Data: singleCount}, - }, distributedTracingSuccessMetrics...)) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "parent.type": "App", - "parent.account": "123", - "parent.app": "456", - "parent.transportType": "HTTP", - "parent.transportDuration": internal.MatchAnything, - "parentId": ip.TransactionID, - "traceId": ip.TransactionID, - "parentSpanId": ip.ID, - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - }, - }}) -} - -func TestPayloadConnectionText(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - payload := makePayload(app, nil) - ip, ok := payload.(internal.Payload) - if !ok { - t.Fatal(payload) - } - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload.Text()) - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, distributedTracingSuccessMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "parent.type": "App", - "parent.account": "123", - "parent.app": "456", - "parent.transportType": "HTTP", - "parent.transportDuration": internal.MatchAnything, - "parentId": ip.TransactionID, - "traceId": ip.TransactionID, - "parentSpanId": ip.ID, - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - }, - }}) -} - -func validBase64(s string) bool { - _, err := base64.StdEncoding.DecodeString(s) - return err == nil -} - -func TestPayloadConnectionHTTPSafe(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - payload := makePayload(app, nil) - ip, ok := payload.(internal.Payload) - if !ok { - t.Fatal(payload) - } - txn := app.StartTransaction("hello", nil, nil) - p := payload.HTTPSafe() - if !validBase64(p) { - t.Error(p) - } - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, distributedTracingSuccessMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "parent.type": "App", - "parent.account": "123", - "parent.app": "456", - "parent.transportType": "HTTP", - "parent.transportDuration": internal.MatchAnything, - "parentId": ip.TransactionID, - "traceId": ip.TransactionID, - "parentSpanId": ip.ID, - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - }, - }}) -} - -func TestPayloadConnectionNotConnected(t *testing.T) { - app := testApp(nil, enableBetterCAT, t) - payload := makePayload(app, nil) - txn := app.StartTransaction("hello", nil, nil) - if nil == payload { - t.Fatal(payload) - } - if "" != payload.Text() { - t.Error(payload.Text()) - } - if "" != payload.HTTPSafe() { - t.Error(payload.HTTPSafe()) - } - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, backgroundMetricsUnknownCaller) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "guid": internal.MatchAnything, - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - }, - }}) -} - -func TestPayloadConnectionBetterCatDisabled(t *testing.T) { - app := testApp(nil, disableCAT, t) - payload := makePayload(app, nil) - txn := app.StartTransaction("hello", nil, nil) - if nil == payload { - t.Fatal(payload) - } - if "" != payload.Text() { - t.Error(payload.Text()) - } - if "" != payload.HTTPSafe() { - t.Error(payload.HTTPSafe()) - } - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if err == nil { - t.Error("missing expected error") - } - if errInboundPayloadDTDisabled != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } -} - -func TestPayloadTransactionsDisabled(t *testing.T) { - cfgFn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Enabled = true - cfg.TransactionEvents.Enabled = false - } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - - payload := txn.CreateDistributedTracePayload() - if nil == payload { - t.Fatal(payload) - } - if "" != payload.Text() { - t.Error(payload.Text()) - } - if "" != payload.HTTPSafe() { - t.Error(payload.HTTPSafe()) - } - err := txn.End() - if nil != err { - t.Error(err) - } -} - -func TestPayloadConnectionEmptyString(t *testing.T) { - app := testApp(nil, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, "") - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, backgroundMetricsUnknownCaller) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "guid": internal.MatchAnything, - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - }, - }}) -} - -func TestCreatePayloadFinished(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - txn.End() - payload := txn.CreateDistributedTracePayload() - if nil == payload { - t.Fatal(payload) - } - if "" != payload.Text() { - t.Error(payload.Text()) - } - if "" != payload.HTTPSafe() { - t.Error(payload.HTTPSafe()) - } -} - -func TestAcceptPayloadFinished(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - payload := makePayload(app, nil) - txn := app.StartTransaction("hello", nil, nil) - err := txn.End() - if nil != err { - t.Error(err) - } - err = txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if err != errAlreadyEnded { - t.Fatal(err) - } - app.ExpectMetrics(t, backgroundMetricsUnknownCaller) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "guid": internal.MatchAnything, - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - }, - }}) -} - -func TestPayloadTypeUnknown(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - invalidPayload := 22 - err := txn.AcceptDistributedTracePayload(TransportHTTP, invalidPayload) - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, backgroundMetricsUnknownCaller) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "guid": internal.MatchAnything, - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - }, - }}) -} - -func TestPayloadAcceptAfterCreate(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - payload := makePayload(app, nil) - txn := app.StartTransaction("hello", nil, nil) - txn.CreateDistributedTracePayload() - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if errOutboundPayloadCreated != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: singleCount}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Ignored/CreateBeforeAccept", Scope: "", Forced: true, Data: singleCount}, - }, backgroundMetricsUnknownCaller...)) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "guid": internal.MatchAnything, - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - }, - }}) -} - -func TestPayloadFromApplicationEmptyTransportType(t *testing.T) { - // A user has two options when it comes to TransportType. They can either use one of the - // defined vars, like TransportHTTP, or create their own empty variable. The name field inside of - // the TransportType struct is not exported outside of the package so users cannot modify its value. - // When they make the attempt, Go reports: - // - // implicit assignment of unexported field 'name' in newrelic.TransportType literal. - // - // This test makes sure an empty TransportType resolves to "Unknown" - var emptyTransport TransportType - - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(emptyTransport, - `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "ac":"123", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - } - }`) - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/App/123/456/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: singleCount}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "parent.type": "App", - "parent.account": "123", - "parent.app": "456", - "parent.transportType": "Unknown", - "parent.transportDuration": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "traceId": "traceID", - "parentSpanId": "id", - "guid": internal.MatchAnything, - }, - }}) -} - -func TestPayloadFutureVersion(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, - `{ - "v":[100,0], - "d":{ - "ty":"App", - "ap":"456", - "ac":"123", - "ti":1488325987402 - } - }`) - if nil == err { - t.Error("missing expected error here") - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/AcceptPayload/Ignored/MajorVersion", Scope: "", Forced: true, Data: singleCount}, - }, backgroundMetricsUnknownCaller...)) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "traceId": internal.MatchAnything, - "guid": internal.MatchAnything, - }, - }}) -} - -func TestPayloadParsingError(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, - `{ - "v":[0,1], - "d":[] - }`) - if nil == err { - t.Error("missing expected parsing error") - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/AcceptPayload/ParseException", Scope: "", Forced: true, Data: singleCount}, - }, backgroundMetricsUnknownCaller...)) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "traceId": internal.MatchAnything, - "guid": internal.MatchAnything, - }, - }}) -} - -func TestPayloadFromFuture(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - payload := makePayload(app, nil) - ip, ok := payload.(internal.Payload) - if !ok { - t.Fatal(payload) - } - ip.Timestamp.Set(time.Now().Add(1 * time.Hour)) - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, ip) - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, distributedTracingSuccessMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "parent.type": "App", - "parent.account": "123", - "parent.app": "456", - "parent.transportType": "HTTP", - "parent.transportDuration": 0, - "parentId": ip.TransactionID, - "traceId": ip.TransactionID, - "parentSpanId": ip.ID, - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - }, - }}) -} - -func TestPayloadUntrustedAccount(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - payload := makePayload(app, nil) - ip, ok := payload.(internal.Payload) - if !ok { - t.Fatal(payload) - } - ip.Account = "12345" - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, ip) - - if err != errTrustedAccountKey { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/AcceptPayload/Ignored/UntrustedAccount", Scope: "", Forced: true, Data: singleCount}, - }, backgroundMetricsUnknownCaller...)) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "guid": internal.MatchAnything, - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - }, - }}) -} - -func TestPayloadMissingVersion(t *testing.T) { - // ensures that a complete distributed trace payload without a version fails - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, - `{ - "d":{ - "ty":"App", - "ap":"456", - "ac":"123", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - } - }`) - if nil == err { - t.Log("Expected error from missing Version (v)") - t.Fail() - } - err = txn.End() - if nil != err { - t.Error(err) - } -} - -func TestTrustedAccountKeyPayloadHasKeyAndMatches(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - // fixture has a "tk" of 123, which matches the trusted_account_key - // from distributedTracingReplyFields. - p := `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "ac":"321", - "id":"id", - "tr":"traceID", - "ti":1488325987402, - "tk":"123" - } - }` - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } -} - -func TestTrustedAccountKeyPayloadHasKeyAndDoesNotMatch(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - // fixture has a "tk" of 1234, which does not match the - // trusted_account_key from distributedTracingReplyFields. - p := `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "ac":"321", - "id":"id", - "tr":"traceID", - "ti":1488325987402, - "tk":"1234" - } - }` - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - if err != errTrustedAccountKey { - t.Error("Expected ErrTrustedAccountKey from mismatched trustkeys", err) - } - err = txn.End() - if nil != err { - t.Error(err) - } -} - -func TestTrustedAccountKeyPayloadMissingKeyAndAccountIdMatches(t *testing.T) { - - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - // fixture has no trust key but its account id of 123 matches - // trusted_account_key from distributedTracingReplyFields. - p := `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "ac":"123", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - } - }` - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - if nil != err { - t.Error(err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - -} - -func TestTrustedAccountKeyPayloadMissingKeyAndAccountIdDoesNotMatch(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - // fixture has no trust key and its account id of 1234 does not match the - // trusted_account_key from distributedTracingReplyFields. - p := `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "ac":"1234", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - } - }` - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - if err != errTrustedAccountKey { - t.Error("Expected ErrTrustedAccountKey from mismatched trustkeys", err) - } - err = txn.End() - if nil != err { - t.Error(err) - } -} - -var ( - backgroundUnknownCaller = []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - } -) - -func TestNilPayload(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, nil) - - if nil != err { - t.Error(err) - } - - err = txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/AcceptPayload/Ignored/Null", Scope: "", Forced: true, Data: singleCount}, - }, backgroundUnknownCaller...)) -} - -func TestNoticeErrorPayload(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - txn := app.StartTransaction("hello", nil, nil) - txn.NoticeError(errors.New("oh no")) - - err := txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Errors/all", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - }, backgroundUnknownCaller...)) -} - -func TestMissingIDsForSupportabilityMetric(t *testing.T) { - p := `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "ac":"123", - "tr":"traceID", - "ti":1488325987402 - } - }` - - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - - if nil == err { - t.Log("Expected error from missing guid and transactionId") - t.Fail() - } - - err = txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/AcceptPayload/ParseException", Scope: "", Forced: true, Data: nil}, - }, backgroundUnknownCaller...)) -} - -func TestMissingVersionForSupportabilityMetric(t *testing.T) { - p := `{ - "d":{ - "ty":"App", - "ap":"456", - "ac":"123", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - } - }` - - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - - if nil == err { - t.Log("Expected error from missing version") - t.Fail() - } - - err = txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/AcceptPayload/ParseException", Scope: "", Forced: true, Data: nil}, - }, backgroundUnknownCaller...)) -} - -func TestMissingFieldForSupportabilityMetric(t *testing.T) { - p := `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - } - }` - - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - - if nil == err { - t.Log("Expected error from missing ac field") - t.Fail() - } - - err = txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/AcceptPayload/ParseException", Scope: "", Forced: true, Data: nil}, - }, backgroundUnknownCaller...)) -} - -func TestParseExceptionSupportabilityMetric(t *testing.T) { - p := `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "id":"id", - "tr":"traceID", - "ti":1488325987402 - } - ` - - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - - if nil == err { - t.Log("Expected error from invalid json") - t.Fail() - } - - err = txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/AcceptPayload/ParseException", Scope: "", Forced: true, Data: nil}, - }, backgroundUnknownCaller...)) -} - -func TestErrorsByCaller(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - txn := app.StartTransaction("hello", nil, nil) - payload := makePayload(app, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload) - - if nil != err { - t.Error(err) - } - - txn.NoticeError(errors.New("oh no")) - - err = txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - - {Name: "TransportDuration/App/123/456/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - - {Name: "ErrorsByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "ErrorsByCaller/App/123/456/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Errors/all", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Errors/OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - }) -} - -func TestCreateDistributedTraceCatDisabled(t *testing.T) { - - // when distributed tracing is disabled, CreateDistributedTracePayload - // should return a value that indicates an empty payload. Examples of - // this depend on language but may be nil/null/None or an empty payload - // object. - - app := testApp(distributedTracingReplyFields, disableCAT, t) - txn := app.StartTransaction("hello", nil, nil) - - p := txn.CreateDistributedTracePayload() - - // empty/shim payload objects return empty strings - if "" != p.Text() { - t.Log("Non empty string response for .Text() method") - t.Fail() - } - - if "" != p.HTTPSafe() { - t.Log("Non empty string response for .HTTPSafe() method") - t.Fail() - } - - err := txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - }) - -} - -func TestCreateDistributedTraceBetterCatDisabled(t *testing.T) { - - // when distributed tracing is disabled, CreateDistributedTracePayload - // should return a value that indicates an empty payload. Examples of - // this depend on language but may be nil/null/None or an empty payload - // object. - - app := testApp(distributedTracingReplyFields, enableOldCATDisableBetterCat, t) - txn := app.StartTransaction("hello", nil, nil) - - p := txn.CreateDistributedTracePayload() - - // empty/shim payload objects return empty strings - if "" != p.Text() { - t.Log("Non empty string response for .Text() method") - t.Fail() - } - - if "" != p.HTTPSafe() { - t.Log("Non empty string response for .HTTPSafe() method") - t.Fail() - } - - err := txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - }) - -} - -func TestCreateDistributedTraceBetterCatEnabled(t *testing.T) { - - // When distributed tracing is enabled and the application is connected, - // CreateDistributedTracePayload should return a valid payload object - - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - - p := txn.CreateDistributedTracePayload() - - // empty/shim payload objects return empty strings - if "" == p.Text() { - t.Log("Empty string response for .Text() method") - t.Fail() - } - - if "" == p.HTTPSafe() { - t.Log("Empty string response for .HTTPSafe() method") - t.Fail() - } - - err := txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }, backgroundUnknownCaller...)) -} - -func isZeroValue(x interface{}) bool { - // https://stackoverflow.com/questions/13901819/quick-way-to-detect-empty-values-via-reflection-in-go - return nil == x || x == reflect.Zero(reflect.TypeOf(x)).Interface() -} - -func testPayloadFieldsPresent(t *testing.T, p DistributedTracePayload, keys ...string) { - out := struct { - Version []int `json:"v"` - Data map[string]interface{} `json:"d"` - }{} - if err := json.Unmarshal([]byte(p.Text()), &out); nil != err { - t.Fatal("unable to unmarshal payload Text", err) - } - for _, key := range keys { - val, ok := out.Data[key] - if !ok { - t.Fatal("required key missing", key) - } - if isZeroValue(val) { - t.Fatal("value has default value", key, val) - } - } -} - -func TestCreateDistributedTraceRequiredFields(t *testing.T) { - - // creates a distributed trace payload and then checks - // to ensure the required fields are in place - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - - p := txn.CreateDistributedTracePayload() - - testPayloadFieldsPresent(t, p, "ty", "ac", "ap", "tr", "ti") - - err := txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }, backgroundUnknownCaller...)) -} - -func TestCreateDistributedTraceTrustKeyAbsent(t *testing.T) { - - // creates a distributed trace payload and then checks - // to ensure the required fields are in place - var payloadData PayloadTest - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - - p := txn.CreateDistributedTracePayload() - - if err := json.Unmarshal([]byte(p.Text()), &payloadData); nil != err { - t.Log("Could not marshall payload into test struct") - t.Error(err) - } - - if nil != payloadData.D["tk"] { - t.Log("Did not expect trust key (tk) to be there") - t.Log(p.Text()) - t.Fail() - } - - err := txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }, backgroundUnknownCaller...)) -} - -func TestCreateDistributedTraceTrustKeyNeeded(t *testing.T) { - - // creates a distributed trace payload and then checks - // to ensure the required fields are in place - var payloadData PayloadTest - app := testApp(distributedTracingReplyFieldsNeedTrustKey, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - - p := txn.CreateDistributedTracePayload() - - if err := json.Unmarshal([]byte(p.Text()), &payloadData); nil != err { - t.Log("Could not marshall payload into test struct") - t.Error(err) - } - - testPayloadFieldsPresent(t, p, "tk") - - err := txn.End() - if nil != err { - t.Error(err) - } - - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Forced: true, Data: nil}, - }, backgroundUnknownCaller...)) -} - -func TestCreateDistributedTraceAfterAcceptSampledTrue(t *testing.T) { - - // simulates 1. reading distributed trace payload from non-header external storage - // (for queues, other customer integrations); 2. Accpeting that Payload; 3. Creating - // a new payload - - // tests that the required fields, plus priority and sampled are set - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - // fixture has a "tk" of 123, which matches the trusted_account_key - // from distributedTracingReplyFields. - p := `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "ac":"321", - "id":"id", - "tr":"traceID", - "ti":1488325987402, - "tk":"123", - "sa":true - } -}` - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - if nil != err { - t.Error(err) - } - - payload := txn.CreateDistributedTracePayload() - - testPayloadFieldsPresent(t, payload, - "ty", "ac", "ap", "tr", "ti", "pr", "sa") - - err = txn.End() - if nil != err { - t.Error(err) - } -} - -func TestCreateDistributedTraceAfterAcceptSampledNotSet(t *testing.T) { - - // simulates 1. reading distributed trace payload from non-header external storage - // (for queues, other customer integrations); 2. Accpeting that Payload; 3. Creating - // a new payload - - // tests that the required fields, plus priority and sampled are set. When "sa" - // is not set, the payload should pickup on sampled value of the transaction - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - - // fixture has a "tk" of 123, which matches the trusted_account_key - // from distributedTracingReplyFields. - p := `{ - "v":[0,1], - "d":{ - "ty":"App", - "ap":"456", - "ac":"321", - "id":"id", - "tr":"traceID", - "ti":1488325987402, - "tk":"123", - "pr":0.54343 - } -}` - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - if nil != err { - t.Error(err) - } - - payload := txn.CreateDistributedTracePayload() - testPayloadFieldsPresent(t, payload, - "ty", "ac", "ap", "id", "tr", "ti", "pr", "sa") - - err = txn.End() - if nil != err { - t.Error(err) - } -} - -type fieldExpectations struct { - Exact map[string]interface{} `json:"exact,omitempty"` - Expected []string `json:"expected,omitempty"` - Unexpected []string `json:"unexpected,omitempty"` -} - -type distributedTraceTestcase struct { - TestName string `json:"test_name"` - Comment string `json:"comment,omitempty"` - TrustedAccountKey string `json:"trusted_account_key"` - AccountID string `json:"account_id"` - WebTransaction bool `json:"web_transaction"` - RaisesException bool `json:"raises_exception"` - ForceSampledTrue bool `json:"force_sampled_true"` - SpanEventsEnabled bool `json:"span_events_enabled"` - MajorVersion int `json:"major_version"` - MinorVersion int `json:"minor_version"` - TransportType string `json:"transport_type"` - InboundPayloads []json.RawMessage `json:"inbound_payloads"` - - OutboundPayloads []fieldExpectations `json:"outbound_payloads,omitempty"` - - Intrinsics struct { - TargetEvents []string `json:"target_events"` - Common *fieldExpectations `json:"common,omitempty"` - Transaction *fieldExpectations `json:"Transaction,omitempty"` - Span *fieldExpectations `json:"Span,omitempty"` - TransactionError *fieldExpectations `json:"TransactionError,omitempty"` - } `json:"intrinsics"` - - ExpectedMetrics [][2]interface{} `json:"expected_metrics"` -} - -func (fe *fieldExpectations) add(intrinsics map[string]interface{}) { - if nil != fe { - for k, v := range fe.Exact { - intrinsics[k] = v - } - for _, v := range fe.Expected { - intrinsics[v] = internal.MatchAnything - } - } -} - -func (fe *fieldExpectations) unexpected() []string { - if nil != fe { - return fe.Unexpected - } - return nil -} - -// getTransport ensures that our transport names match cross agent test values. -func getTransport(transport string) TransportType { - switch transport { - case TransportHTTP.name: - return TransportHTTP - case TransportHTTPS.name: - return TransportHTTPS - case TransportKafka.name: - return TransportKafka - case TransportJMS.name: - return TransportJMS - case TransportIronMQ.name: - return TransportIronMQ - case TransportAMQP.name: - return TransportAMQP - case TransportQueue.name: - return TransportQueue - case TransportOther.name: - return TransportOther - default: - return TransportUnknown - } -} - -func runDistributedTraceCrossAgentTestcase(tst *testing.T, tc distributedTraceTestcase, extraAsserts func(expectApp, internal.Validator)) { - t := internal.ExtendValidator(tst, "test="+tc.TestName) - configCallback := enableBetterCAT - if false == tc.SpanEventsEnabled { - configCallback = disableSpanEvents - } - - app := testApp(func(reply *internal.ConnectReply) { - reply.AccountID = tc.AccountID - reply.AppID = "456" - reply.PrimaryAppID = "456" - reply.TrustedAccountKey = tc.TrustedAccountKey - - // if cross agent tests ever include logic for sampling - // we'll need to revisit this testing sampler - reply.AdaptiveSampler = internal.SampleEverything{} - - }, configCallback, tst) - - txn := app.StartTransaction("hello", nil, nil) - if tc.WebTransaction { - txn.SetWebRequest(nil) - } - - // If the tests wants us to have an error, give 'em an error - if tc.RaisesException { - txn.NoticeError(errors.New("my error message")) - } - - // If there are no inbound payloads, invoke Accept on an empty inbound payload. - if nil == tc.InboundPayloads { - txn.AcceptDistributedTracePayload(getTransport(tc.TransportType), nil) - } - - for _, value := range tc.InboundPayloads { - // Note that the error return value is not tested here because - // some of the tests are intentionally errors. - txn.AcceptDistributedTracePayload(getTransport(tc.TransportType), string(value)) - } - - //call create each time an outbound payload appears in the testcase - for _, expect := range tc.OutboundPayloads { - actual := txn.CreateDistributedTracePayload().Text() - assertTestCaseOutboundPayload(expect, t, actual) - } - - err := txn.End() - if nil != err { - t.Error(err) - } - - // create WantMetrics and assert - wantMetrics := []internal.WantMetric{} - for _, metric := range tc.ExpectedMetrics { - wantMetrics = append(wantMetrics, - internal.WantMetric{Name: metric[0].(string), Scope: "", Forced: nil, Data: nil}) - } - app.ExpectMetricsPresent(t, wantMetrics) - - // Add extra fields that are not listed in the JSON file so that we can - // always do exact intrinsic set match. - - extraTxnFields := &fieldExpectations{Expected: []string{"name"}} - if tc.WebTransaction { - extraTxnFields.Expected = append(extraTxnFields.Expected, "nr.apdexPerfZone") - } - - extraSpanFields := &fieldExpectations{ - Expected: []string{"name", "category", "nr.entryPoint"}, - } - - // There is a single test with an error (named "exception"), so these - // error expectations can be hard coded. TODO: Move some of these. - // fields into the cross agent tests. - extraErrorFields := &fieldExpectations{ - Expected: []string{"parent.type", "parent.account", "parent.app", - "parent.transportType", "error.message", "transactionName", - "parent.transportDuration", "error.class"}, - } - - for _, value := range tc.Intrinsics.TargetEvents { - switch value { - case "Transaction": - assertTestCaseIntrinsics(t, - app.ExpectTxnEvents, - tc.Intrinsics.Common, - tc.Intrinsics.Transaction, - extraTxnFields) - case "Span": - assertTestCaseIntrinsics(t, - app.ExpectSpanEvents, - tc.Intrinsics.Common, - tc.Intrinsics.Span, - extraSpanFields) - - case "TransactionError": - assertTestCaseIntrinsics(t, - app.ExpectErrorEvents, - tc.Intrinsics.Common, - tc.Intrinsics.TransactionError, - extraErrorFields) - } - } - - extraAsserts(app, t) -} - -func assertTestCaseOutboundPayload(expect fieldExpectations, t internal.Validator, actual string) { - type outboundTestcase struct { - Version [2]uint `json:"v"` - Data map[string]interface{} `json:"d"` - } - var actualPayload outboundTestcase - err := json.Unmarshal([]byte(actual), &actualPayload) - if nil != err { - t.Error(err) - } - // Affirm that the exact values are in the payload. - for k, v := range expect.Exact { - if k != "v" { - field := strings.Split(k, ".")[1] - if v != actualPayload.Data[field] { - t.Error(fmt.Sprintf("exact outbound payload field mismatch key=%s wanted=%v got=%v", - k, v, actualPayload.Data[field])) - } - } - } - // Affirm that the expected values are in the actual payload. - for _, e := range expect.Expected { - field := strings.Split(e, ".")[1] - if nil == actualPayload.Data[field] { - t.Error(fmt.Sprintf("expected outbound payload field missing key=%s", e)) - } - } - // Affirm that the unexpected values are not in the actual payload. - for _, u := range expect.Unexpected { - field := strings.Split(u, ".")[1] - if nil != actualPayload.Data[field] { - t.Error(fmt.Sprintf("unexpected outbound payload field present key=%s", u)) - } - } -} - -func assertTestCaseIntrinsics(t internal.Validator, - expect func(internal.Validator, []internal.WantEvent), - fields ...*fieldExpectations) { - - intrinsics := map[string]interface{}{} - for _, f := range fields { - f.add(intrinsics) - } - expect(t, []internal.WantEvent{{Intrinsics: intrinsics}}) -} - -func TestDistributedTraceCrossAgent(t *testing.T) { - var tcs []distributedTraceTestcase - data, err := crossagent.ReadFile(`distributed_tracing/distributed_tracing.json`) - if nil != err { - t.Fatal(err) - } - if err := json.Unmarshal(data, &tcs); nil != err { - t.Fatal(err) - } - // Test that we are correctly parsing all of the testcase fields by - // comparing an opaque object from original JSON to an object from JSON - // created by our testcases. - backToJSON, err := json.Marshal(tcs) - if nil != err { - t.Fatal(err) - } - var fromFile []map[string]interface{} - var fromMarshalled []map[string]interface{} - if err := json.Unmarshal(data, &fromFile); nil != err { - t.Fatal(err) - } - if err := json.Unmarshal(backToJSON, &fromMarshalled); nil != err { - t.Fatal(err) - } - if !reflect.DeepEqual(fromFile, fromMarshalled) { - t.Error(internal.CompactJSONString(string(data)), "\n", - internal.CompactJSONString(string(backToJSON))) - } - - // Iterate over all cross-agent tests - for _, tc := range tcs { - extraAsserts := func(app expectApp, t internal.Validator) {} - if "spans_disabled_in_child" == tc.TestName { - // if span events are disabled but distributed tracing is enabled, then - // we expect there are zero span events - extraAsserts = func(app expectApp, t internal.Validator) { - app.ExpectSpanEvents(t, nil) - } - } - runDistributedTraceCrossAgentTestcase(t, tc, extraAsserts) - } -} - -func TestDistributedTraceDisabledSpanEventsEnabled(t *testing.T) { - app := testApp(distributedTracingReplyFields, disableDistributedTracerEnableSpanEvents, t) - payload := makePayload(app, nil) - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if err != errInboundPayloadDTDisabled { - t.Fatal("we expected an error with DT disabled", err) - } - err = txn.End() - if nil != err { - t.Error(err) - } - - // ensure no span events created - app.ExpectSpanEvents(t, nil) -} - -func TestCreatePayloadAppNotConnected(t *testing.T) { - // Test that an app which isn't connected does not create distributed - // trace payloads. - app := testApp(nil, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - payload := txn.CreateDistributedTracePayload() - if payload.Text() != "" || payload.HTTPSafe() != "" { - t.Error(payload.Text(), payload.HTTPSafe()) - } -} -func TestCreatePayloadReplyMissingTrustKey(t *testing.T) { - // Test that an app whose reply is missing the trust key does not create - // distributed trace payloads. - app := testApp(func(reply *internal.ConnectReply) { - distributedTracingReplyFields(reply) - reply.TrustedAccountKey = "" - }, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - payload := txn.CreateDistributedTracePayload() - if payload.Text() != "" || payload.HTTPSafe() != "" { - t.Error(payload.Text(), payload.HTTPSafe()) - } -} - -func TestAcceptPayloadAppNotConnected(t *testing.T) { - // Test that an app which isn't connected does not accept distributed - // trace payloads. - app := testApp(nil, enableBetterCAT, t) - payload := testApp(distributedTracingReplyFields, enableBetterCAT, t). - StartTransaction("name", nil, nil). - CreateDistributedTracePayload() - if payload.Text() == "" { - t.Fatal(payload) - } - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectMetrics(t, backgroundUnknownCaller) -} - -func TestAcceptPayloadReplyMissingTrustKey(t *testing.T) { - // Test that an app whose reply is missing a trust key does not accept - // distributed trace payloads. - app := testApp(func(reply *internal.ConnectReply) { - distributedTracingReplyFields(reply) - reply.TrustedAccountKey = "" - }, enableBetterCAT, t) - payload := testApp(distributedTracingReplyFields, enableBetterCAT, t). - StartTransaction("name", nil, nil). - CreateDistributedTracePayload() - if payload.Text() == "" { - t.Fatal(payload) - } - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, payload) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectMetrics(t, backgroundUnknownCaller) -} diff --git a/internal_errors_13_test.go b/internal_errors_13_test.go deleted file mode 100644 index 7df35a227..000000000 --- a/internal_errors_13_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.13 - -package newrelic - -import ( - "fmt" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -func TestNoticedWrappedError(t *testing.T) { - gamma := func() error { - return Error{ - Message: "socket error", - Class: "socketError", - Attributes: map[string]interface{}{ - "zip": "zap", - }, - } - } - beta := func() error { return fmt.Errorf("problem in beta: %w", gamma()) } - alpha := func() error { return fmt.Errorf("problem in alpha: %w", beta()) } - - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(alpha()) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "problem in alpha: problem in beta: socket error", - Klass: "socketError", - UserAttributes: map[string]interface{}{ - "zip": "zap", - }, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "socketError", - "error.message": "problem in alpha: problem in beta: socket error", - "transactionName": "OtherTransaction/Go/hello", - }, - UserAttributes: map[string]interface{}{ - "zip": "zap", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} diff --git a/internal_errors_stacktrace_test.go b/internal_errors_stacktrace_test.go deleted file mode 100644 index 431ccafe2..000000000 --- a/internal_errors_stacktrace_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.7 - -package newrelic - -import ( - "runtime" - "strings" - "testing" -) - -// The use of runtime.CallersFrames requires Go 1.7+. - -func topFrameFunction(stack []uintptr) string { - var frame runtime.Frame - frames := runtime.CallersFrames(stack) - if nil != frames { - frame, _ = frames.Next() - } - return frame.Function -} - -type withStackAndCause struct { - cause error - stack []uintptr -} - -type withStack struct { - stack []uintptr -} - -func (e withStackAndCause) Error() string { return e.cause.Error() } -func (e withStackAndCause) StackTrace() []uintptr { return e.stack } -func (e withStackAndCause) Unwrap() error { return e.cause } - -func (e withStack) Error() string { return "something went wrong" } -func (e withStack) StackTrace() []uintptr { return e.stack } - -func generateStack() []uintptr { - skip := 2 // skip runtime.Callers and this function. - callers := make([]uintptr, 20) - written := runtime.Callers(skip, callers) - return callers[:written] -} - -func alpha() []uintptr { return generateStack() } -func beta() []uintptr { return generateStack() } - -func TestStackTrace(t *testing.T) { - // First choice is any StackTrace() of the immediate error. - // Second choice is any StackTrace() of the error's cause. - // Final choice is stack trace of the current location. - testcases := []struct { - Error error - ExpectTopFrame string - }{ - {Error: basicError{}, ExpectTopFrame: "internal.GetStackTrace"}, - {Error: withStack{stack: alpha()}, ExpectTopFrame: "alpha"}, - {Error: withStack{stack: nil}, ExpectTopFrame: "internal.GetStackTrace"}, - {Error: withStackAndCause{stack: alpha(), cause: basicError{}}, ExpectTopFrame: "alpha"}, - {Error: withStackAndCause{stack: nil, cause: withStack{stack: beta()}}, ExpectTopFrame: "beta"}, - {Error: withStackAndCause{stack: nil, cause: withStack{stack: nil}}, ExpectTopFrame: "internal.GetStackTrace"}, - } - - for idx, tc := range testcases { - data, err := errDataFromError(tc.Error) - if err != nil { - t.Errorf("testcase %d: got error: %v", idx, err) - continue - } - fn := topFrameFunction(data.Stack) - if !strings.Contains(fn, tc.ExpectTopFrame) { - t.Errorf("testcase %d: expected %s got %s", - idx, tc.ExpectTopFrame, fn) - } - } -} diff --git a/internal_errors_test.go b/internal_errors_test.go deleted file mode 100644 index 7a76c7bbb..000000000 --- a/internal_errors_test.go +++ /dev/null @@ -1,669 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/json" - "runtime" - "strconv" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -type myError struct{} - -func (e myError) Error() string { return "my msg" } - -func TestNoticeErrorBackground(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNoticeErrorWeb(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - err := txn.NoticeError(myError{}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - }, - AgentAttributes: helloRequestAttributes, - }}) - app.ExpectMetrics(t, webErrorMetrics) -} - -func TestNoticeErrorTxnEnded(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - txn.End() - err := txn.NoticeError(myError{}) - if err != errAlreadyEnded { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, backgroundMetrics) -} - -func TestNoticeErrorHighSecurity(t *testing.T) { - cfgFn := func(cfg *Config) { cfg.HighSecurity = true } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: highSecurityErrorMsg, - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": highSecurityErrorMsg, - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNoticeErrorMessageSecurityPolicy(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { reply.SecurityPolicies.AllowRawExceptionMessages.SetEnabled(false) } - app := testApp(replyfn, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: securityPolicyErrorMsg, - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": securityPolicyErrorMsg, - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNoticeErrorLocallyDisabled(t *testing.T) { - cfgFn := func(cfg *Config) { cfg.ErrorCollector.Enabled = false } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if errorsDisabled != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, backgroundMetrics) -} - -func TestErrorsDisabledByServerSideConfig(t *testing.T) { - // Test that errors can be disabled by server-side-config. - cfgFn := func(cfg *Config) {} - replyfn := func(reply *internal.ConnectReply) { - json.Unmarshal([]byte(`{"agent_config":{"error_collector.enabled":false}}`), reply) - } - app := testApp(replyfn, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if errorsDisabled != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, backgroundMetrics) -} - -func TestErrorsEnabledByServerSideConfig(t *testing.T) { - // Test that errors can be enabled by server-side-config. - cfgFn := func(cfg *Config) { - cfg.ErrorCollector.Enabled = false - } - replyfn := func(reply *internal.ConnectReply) { - json.Unmarshal([]byte(`{"agent_config":{"error_collector.enabled":true}}`), reply) - } - app := testApp(replyfn, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNoticeErrorTracedErrorsRemotelyDisabled(t *testing.T) { - // This tests that the connect reply field "collect_errors" controls the - // collection of traced-errors, not error-events. - replyfn := func(reply *internal.ConnectReply) { reply.CollectErrors = false } - app := testApp(replyfn, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if err != nil { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNoticeErrorNil(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(nil) - if errNilError != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, backgroundMetrics) -} - -func TestNoticeErrorEventsLocallyDisabled(t *testing.T) { - cfgFn := func(cfg *Config) { cfg.ErrorCollector.CaptureEvents = false } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNoticeErrorEventsRemotelyDisabled(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { reply.CollectErrorEvents = false } - app := testApp(replyfn, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -type errorWithClass struct{ class string } - -func (e errorWithClass) Error() string { return "my msg" } -func (e errorWithClass) ErrorClass() string { return e.class } - -func TestErrorWithClasser(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(errorWithClass{class: "zap"}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "zap", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "zap", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestErrorWithClasserReturnsEmpty(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(errorWithClass{class: ""}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.errorWithClass", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.errorWithClass", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -type withStackTrace struct{ trace []uintptr } - -func makeErrorWithStackTrace() error { - callers := make([]uintptr, 20) - written := runtime.Callers(1, callers) - return withStackTrace{ - trace: callers[0:written], - } -} - -func (e withStackTrace) Error() string { return "my msg" } -func (e withStackTrace) StackTrace() []uintptr { return e.trace } - -func TestErrorWithStackTrace(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - e := makeErrorWithStackTrace() - err := txn.NoticeError(e) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.withStackTrace", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.withStackTrace", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestErrorWithStackTraceReturnsNil(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - e := withStackTrace{trace: nil} - err := txn.NoticeError(e) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.withStackTrace", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.withStackTrace", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNewrelicErrorNoAttributes(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(Error{ - Message: "my msg", - Class: "my class", - }) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "my class", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "my class", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNewrelicErrorValidAttributes(t *testing.T) { - extraAttributes := map[string]interface{}{ - "zip": "zap", - } - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(Error{ - Message: "my msg", - Class: "my class", - Attributes: extraAttributes, - }) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "my class", - UserAttributes: extraAttributes, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "my class", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - UserAttributes: extraAttributes, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNewrelicErrorAttributesHighSecurity(t *testing.T) { - extraAttributes := map[string]interface{}{ - "zip": "zap", - } - cfgFn := func(cfg *Config) { cfg.HighSecurity = true } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(Error{ - Message: "my msg", - Class: "my class", - Attributes: extraAttributes, - }) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "message removed by high security setting", - Klass: "my class", - UserAttributes: map[string]interface{}{}, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "my class", - "error.message": "message removed by high security setting", - "transactionName": "OtherTransaction/Go/hello", - }, - UserAttributes: map[string]interface{}{}, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNewrelicErrorAttributesSecurityPolicy(t *testing.T) { - extraAttributes := map[string]interface{}{ - "zip": "zap", - } - replyfn := func(reply *internal.ConnectReply) { reply.SecurityPolicies.CustomParameters.SetEnabled(false) } - app := testApp(replyfn, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(Error{ - Message: "my msg", - Class: "my class", - Attributes: extraAttributes, - }) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "my class", - UserAttributes: map[string]interface{}{}, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "my class", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - UserAttributes: map[string]interface{}{}, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNewrelicErrorAttributeOverridesNormalAttribute(t *testing.T) { - extraAttributes := map[string]interface{}{ - "zip": "zap", - } - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - if err := txn.AddAttribute("zip", 123); nil != err { - t.Error(err) - } - err := txn.NoticeError(Error{ - Message: "my msg", - Class: "my class", - Attributes: extraAttributes, - }) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "my class", - UserAttributes: extraAttributes, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "my class", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - UserAttributes: extraAttributes, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestNewrelicErrorInvalidAttributes(t *testing.T) { - extraAttributes := map[string]interface{}{ - "zip": "zap", - "INVALID": struct{}{}, - } - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(Error{ - Message: "my msg", - Class: "my class", - Attributes: extraAttributes, - }) - if _, ok := err.(internal.ErrInvalidAttributeType); !ok { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, backgroundMetrics) -} - -func TestExtraErrorAttributeRemovedThroughConfiguration(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.ErrorCollector.Attributes.Exclude = []string{"IGNORE_ME"} - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(Error{ - Message: "my msg", - Class: "my class", - Attributes: map[string]interface{}{ - "zip": "zap", - "IGNORE_ME": 123, - }, - }) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "my class", - UserAttributes: map[string]interface{}{"zip": "zap"}, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "my class", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - UserAttributes: map[string]interface{}{"zip": "zap"}, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) - -} - -func TestTooManyExtraErrorAttributes(t *testing.T) { - attrs := make(map[string]interface{}) - for i := 0; i <= internal.AttributeErrorLimit; i++ { - attrs[strconv.Itoa(i)] = i - } - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(Error{ - Message: "my msg", - Class: "my class", - Attributes: attrs, - }) - if errTooManyErrorAttributes != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, backgroundMetrics) -} - -type basicError struct{} - -func (e basicError) Error() string { return "something went wrong" } - -type withClass struct{ class string } - -func (e withClass) Error() string { return "something went wrong" } -func (e withClass) ErrorClass() string { return e.class } - -type withClassAndCause struct { - cause error - class string -} - -func (e withClassAndCause) Error() string { return e.cause.Error() } -func (e withClassAndCause) Unwrap() error { return e.cause } -func (e withClassAndCause) ErrorClass() string { return e.class } - -type withCause struct{ cause error } - -func (e withCause) Error() string { return e.cause.Error() } -func (e withCause) Unwrap() error { return e.cause } - -func errWithClass(class string) error { return withClass{class: class} } -func wrapWithClass(e error, class string) error { return withClassAndCause{cause: e, class: class} } -func wrapError(e error) error { return withCause{cause: e} } - -func TestErrorClass(t *testing.T) { - // First choice is any ErrorClass() of the immediate error. - // Second choice is any ErrorClass() of the error's cause. - // Final choice is the reflect type of the error's cause. - testcases := []struct { - Error error - Expect string - }{ - {Error: basicError{}, Expect: "newrelic.basicError"}, - {Error: errWithClass("zap"), Expect: "zap"}, - {Error: errWithClass(""), Expect: "newrelic.withClass"}, - {Error: wrapWithClass(errWithClass("zap"), "zip"), Expect: "zip"}, - {Error: wrapWithClass(errWithClass("zap"), ""), Expect: "zap"}, - {Error: wrapWithClass(errWithClass(""), ""), Expect: "newrelic.withClass"}, - {Error: wrapError(basicError{}), Expect: "newrelic.basicError"}, - {Error: wrapError(errWithClass("zap")), Expect: "zap"}, - } - - for idx, tc := range testcases { - data, err := errDataFromError(tc.Error) - if err != nil { - t.Errorf("testcase %d: got error: %v", idx, err) - continue - } - if data.Klass != tc.Expect { - t.Errorf("testcase %d: expected %s got %s", idx, tc.Expect, data.Klass) - } - } -} diff --git a/internal_response_writer.go b/internal_response_writer.go deleted file mode 100644 index b80a57cb4..000000000 --- a/internal_response_writer.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "bufio" - "io" - "net" - "net/http" - - "github.com/newrelic/go-agent/internal" -) - -func (thd *thread) CloseNotify() <-chan bool { - return thd.txn.getWriter().(http.CloseNotifier).CloseNotify() -} -func (thd *thread) Flush() { - thd.txn.getWriter().(http.Flusher).Flush() -} -func (thd *thread) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return thd.txn.getWriter().(http.Hijacker).Hijack() -} -func (thd *thread) ReadFrom(r io.Reader) (int64, error) { - return thd.txn.getWriter().(io.ReaderFrom).ReadFrom(r) -} - -type threadWithExtras interface { - Transaction - internal.AddAgentAttributer - internal.AddAgentSpanAttributer -} - -func upgradeTxn(thd *thread) Transaction { - // Note that thd.txn.getWriter() is not used here. The transaction is - // locked (or under construction) when this function is used. - - // GENERATED CODE DO NOT MODIFY - // This code generated by internal/tools/interface-wrapping - var ( - i0 int32 = 1 << 0 - i1 int32 = 1 << 1 - i2 int32 = 1 << 2 - i3 int32 = 1 << 3 - ) - var interfaceSet int32 - if _, ok := thd.txn.writer.(http.CloseNotifier); ok { - interfaceSet |= i0 - } - if _, ok := thd.txn.writer.(http.Flusher); ok { - interfaceSet |= i1 - } - if _, ok := thd.txn.writer.(http.Hijacker); ok { - interfaceSet |= i2 - } - if _, ok := thd.txn.writer.(io.ReaderFrom); ok { - interfaceSet |= i3 - } - switch interfaceSet { - default: // No optional interfaces implemented - return struct { - threadWithExtras - }{thd} - case i0: - return struct { - threadWithExtras - http.CloseNotifier - }{thd, thd} - case i1: - return struct { - threadWithExtras - http.Flusher - }{thd, thd} - case i0 | i1: - return struct { - threadWithExtras - http.CloseNotifier - http.Flusher - }{thd, thd, thd} - case i2: - return struct { - threadWithExtras - http.Hijacker - }{thd, thd} - case i0 | i2: - return struct { - threadWithExtras - http.CloseNotifier - http.Hijacker - }{thd, thd, thd} - case i1 | i2: - return struct { - threadWithExtras - http.Flusher - http.Hijacker - }{thd, thd, thd} - case i0 | i1 | i2: - return struct { - threadWithExtras - http.CloseNotifier - http.Flusher - http.Hijacker - }{thd, thd, thd, thd} - case i3: - return struct { - threadWithExtras - io.ReaderFrom - }{thd, thd} - case i0 | i3: - return struct { - threadWithExtras - http.CloseNotifier - io.ReaderFrom - }{thd, thd, thd} - case i1 | i3: - return struct { - threadWithExtras - http.Flusher - io.ReaderFrom - }{thd, thd, thd} - case i0 | i1 | i3: - return struct { - threadWithExtras - http.CloseNotifier - http.Flusher - io.ReaderFrom - }{thd, thd, thd, thd} - case i2 | i3: - return struct { - threadWithExtras - http.Hijacker - io.ReaderFrom - }{thd, thd, thd} - case i0 | i2 | i3: - return struct { - threadWithExtras - http.CloseNotifier - http.Hijacker - io.ReaderFrom - }{thd, thd, thd, thd} - case i1 | i2 | i3: - return struct { - threadWithExtras - http.Flusher - http.Hijacker - io.ReaderFrom - }{thd, thd, thd, thd} - case i0 | i1 | i2 | i3: - return struct { - threadWithExtras - http.CloseNotifier - http.Flusher - http.Hijacker - io.ReaderFrom - }{thd, thd, thd, thd, thd} - } -} diff --git a/internal_response_writer_test.go b/internal_response_writer_test.go deleted file mode 100644 index a5c54a815..000000000 --- a/internal_response_writer_test.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "bufio" - "io" - "net" - "net/http" - "testing" -) - -type rwNoExtraMethods struct { - hijackCalled bool - readFromCalled bool - flushCalled bool - closeNotifyCalled bool -} - -type rwTwoExtraMethods struct{ rwNoExtraMethods } -type rwAllExtraMethods struct{ rwTwoExtraMethods } - -func (rw *rwAllExtraMethods) CloseNotify() <-chan bool { - rw.closeNotifyCalled = true - return nil -} -func (rw *rwAllExtraMethods) ReadFrom(r io.Reader) (int64, error) { - rw.readFromCalled = true - return 0, nil -} - -func (rw *rwNoExtraMethods) Header() http.Header { return nil } -func (rw *rwNoExtraMethods) Write([]byte) (int, error) { return 0, nil } -func (rw *rwNoExtraMethods) WriteHeader(statusCode int) {} - -func (rw *rwTwoExtraMethods) Flush() { - rw.flushCalled = true -} -func (rw *rwTwoExtraMethods) Hijack() (net.Conn, *bufio.ReadWriter, error) { - rw.hijackCalled = true - return nil, nil, nil -} - -func TestTransactionAllExtraMethods(t *testing.T) { - app := testApp(nil, nil, t) - rw := &rwAllExtraMethods{} - txn := app.StartTransaction("hello", rw, nil) - if v, ok := txn.(http.CloseNotifier); ok { - v.CloseNotify() - } - if v, ok := txn.(http.Flusher); ok { - v.Flush() - } - if v, ok := txn.(http.Hijacker); ok { - v.Hijack() - } - if v, ok := txn.(io.ReaderFrom); ok { - v.ReadFrom(nil) - } - if !rw.hijackCalled || - !rw.readFromCalled || - !rw.flushCalled || - !rw.closeNotifyCalled { - t.Error("wrong methods called", rw) - } -} - -func TestTransactionNoExtraMethods(t *testing.T) { - app := testApp(nil, nil, t) - rw := &rwNoExtraMethods{} - txn := app.StartTransaction("hello", rw, nil) - if _, ok := txn.(http.CloseNotifier); ok { - t.Error("unexpected CloseNotifier method") - } - if _, ok := txn.(http.Flusher); ok { - t.Error("unexpected Flusher method") - } - if _, ok := txn.(http.Hijacker); ok { - t.Error("unexpected Hijacker method") - } - if _, ok := txn.(io.ReaderFrom); ok { - t.Error("unexpected ReaderFrom method") - } -} - -func TestTransactionTwoExtraMethods(t *testing.T) { - app := testApp(nil, nil, t) - rw := &rwTwoExtraMethods{} - txn := app.StartTransaction("hello", rw, nil) - if _, ok := txn.(http.CloseNotifier); ok { - t.Error("unexpected CloseNotifier method") - } - if v, ok := txn.(http.Flusher); ok { - v.Flush() - } - if v, ok := txn.(http.Hijacker); ok { - v.Hijack() - } - if _, ok := txn.(io.ReaderFrom); ok { - t.Error("unexpected ReaderFrom method") - } - if !rw.hijackCalled || - rw.readFromCalled || - !rw.flushCalled || - rw.closeNotifyCalled { - t.Error("wrong methods called", rw) - } -} diff --git a/internal_segment_attributes_test.go b/internal_segment_attributes_test.go deleted file mode 100644 index 422ae20e4..000000000 --- a/internal_segment_attributes_test.go +++ /dev/null @@ -1,509 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/json" - "net/http" - "testing" - "time" - - "github.com/newrelic/go-agent/internal" -) - -func TestTraceSegments(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.StackTraceThreshold = 0 - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - - // Disable span event attributes to ensure they are separate. - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Attributes.Enabled = false - - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - basicSegment := StartSegment(txn, "basic") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west") - basicSegment.End() - datastoreSegment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - QueryParameters: map[string]interface{}{"zap": "zip"}, - } - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123") - datastoreSegment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - externalSegment := StartExternalSegment(txn, req) - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret") - externalSegment.End() - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/hello", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/hello", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "Custom/basic", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - "aws.region": "west", - }, - }, - { - SegmentName: "Datastore/statement/MySQL/mycollection/myoperation", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - "query_parameters": "map[zap:zip]", - "peer.address": "myhost:myport", - "peer.hostname": "myhost", - "db.statement": "myquery", - "db.instance": "dbname", - "aws.requestId": 123, - }, - }, - { - SegmentName: "External/example.com/http/GET", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - "http.url": "http://example.com", - "aws.operation": "secret", - }, - }, - }, - }}, - }, - }}) -} - -func TestTraceSegmentsNoBacktrace(t *testing.T) { - // Test that backtrace will only appear if the segment's duration - // exceeds TransactionTracer.StackTraceThreshold. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.StackTraceThreshold = 1 * time.Hour - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - - // Disable span event attributes to ensure they are separate. - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Attributes.Enabled = false - - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - basicSegment := StartSegment(txn, "basic") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west") - basicSegment.End() - datastoreSegment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - QueryParameters: map[string]interface{}{"zap": "zip"}, - } - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123") - datastoreSegment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - externalSegment := StartExternalSegment(txn, req) - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret") - externalSegment.End() - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/hello", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/hello", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "Custom/basic", - Attributes: map[string]interface{}{ - "aws.region": "west", - }, - }, - { - SegmentName: "Datastore/statement/MySQL/mycollection/myoperation", - Attributes: map[string]interface{}{ - "query_parameters": "map[zap:zip]", - "peer.address": "myhost:myport", - "peer.hostname": "myhost", - "db.statement": "myquery", - "db.instance": "dbname", - "aws.requestId": 123, - }, - }, - { - SegmentName: "External/example.com/http/GET", - Attributes: map[string]interface{}{ - "http.url": "http://example.com", - "aws.operation": "secret", - }, - }, - }, - }}, - }, - }}) -} - -func TestTraceStacktraceServerSideConfig(t *testing.T) { - // Test that the server-side-config stack trace threshold is observed. - replyfn := func(reply *internal.ConnectReply) { - json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.stack_trace_threshold":0}}`), reply) - } - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.StackTraceThreshold = 1 * time.Hour - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - basicSegment := StartSegment(txn, "basic") - basicSegment.End() - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/hello", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/hello", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "Custom/basic", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - }, - }, - }, - }}, - }, - }}) -} - -func TestTraceSegmentAttributesExcluded(t *testing.T) { - // Test that segment attributes can be excluded by Attributes.Exclude. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.StackTraceThreshold = 1 * time.Hour - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.Attributes.Exclude = []string{ - SpanAttributeDBStatement, - SpanAttributeDBInstance, - SpanAttributeDBCollection, - SpanAttributePeerAddress, - SpanAttributePeerHostname, - SpanAttributeHTTPURL, - SpanAttributeHTTPMethod, - SpanAttributeAWSOperation, - SpanAttributeAWSRequestID, - SpanAttributeAWSRegion, - "query_parameters", - } - - // Disable span event attributes to ensure they are separate. - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Attributes.Enabled = false - - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - basicSegment := StartSegment(txn, "basic") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west") - basicSegment.End() - datastoreSegment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - QueryParameters: map[string]interface{}{"zap": "zip"}, - } - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123") - datastoreSegment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - externalSegment := StartExternalSegment(txn, req) - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret") - externalSegment.End() - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/hello", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/hello", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "Custom/basic", - Attributes: map[string]interface{}{}, - }, - { - SegmentName: "Datastore/statement/MySQL/mycollection/myoperation", - Attributes: map[string]interface{}{}, - }, - { - SegmentName: "External/example.com/http/GET", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) -} - -func TestTraceSegmentAttributesSpecificallyExcluded(t *testing.T) { - // Test that segment attributes can be excluded by - // TransactionTracer.Segments.Attributes.Exclude. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.StackTraceThreshold = 1 * time.Hour - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.TransactionTracer.Segments.Attributes.Exclude = []string{ - SpanAttributeDBStatement, - SpanAttributeDBInstance, - SpanAttributeDBCollection, - SpanAttributePeerAddress, - SpanAttributePeerHostname, - SpanAttributeHTTPURL, - SpanAttributeHTTPMethod, - SpanAttributeAWSOperation, - SpanAttributeAWSRequestID, - SpanAttributeAWSRegion, - "query_parameters", - } - - // Disable span event attributes to ensure they are separate. - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Attributes.Enabled = false - - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - basicSegment := StartSegment(txn, "basic") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west") - basicSegment.End() - datastoreSegment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - QueryParameters: map[string]interface{}{"zap": "zip"}, - } - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123") - datastoreSegment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - externalSegment := StartExternalSegment(txn, req) - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret") - externalSegment.End() - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/hello", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/hello", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "Custom/basic", - Attributes: map[string]interface{}{}, - }, - { - SegmentName: "Datastore/statement/MySQL/mycollection/myoperation", - Attributes: map[string]interface{}{}, - }, - { - SegmentName: "External/example.com/http/GET", - Attributes: map[string]interface{}{}, - }, - }, - }}, - }, - }}) -} - -func TestTraceSegmentAttributesDisabled(t *testing.T) { - // Test that segment attributes can be disabled by Attributes.Enabled - // but backtrace and transaction_guid still appear. - cfgfn := func(cfg *Config) { - cfg.Attributes.Enabled = false - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.StackTraceThreshold = 0 - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - } - app := testApp(crossProcessReplyFn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - basicSegment := StartSegment(txn, "basic") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west") - basicSegment.End() - datastoreSegment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - QueryParameters: map[string]interface{}{"zap": "zip"}, - } - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123") - datastoreSegment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - externalSegment := StartExternalSegment(txn, req) - externalSegment.Response = &http.Response{ - Header: outboundCrossProcessResponse(), - } - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret") - externalSegment.End() - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/hello", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/hello", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "Custom/basic", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - }, - }, - { - SegmentName: "Datastore/statement/MySQL/mycollection/myoperation", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - }, - }, - { - SegmentName: "ExternalTransaction/example.com/12345#67890/WebTransaction/Go/txn", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - "transaction_guid": internal.MatchAnything, - }, - }, - }, - }}, - }, - }}) -} - -func TestTraceSegmentAttributesSpecificallyDisabled(t *testing.T) { - // Test that segment attributes can be disabled by - // TransactionTracer.Segments.Attributes.Enabled but backtrace and - // transaction_guid still appear. - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Segments.Attributes.Enabled = false - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.StackTraceThreshold = 0 - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - } - app := testApp(crossProcessReplyFn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - basicSegment := StartSegment(txn, "basic") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west") - basicSegment.End() - datastoreSegment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - QueryParameters: map[string]interface{}{"zap": "zip"}, - } - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123") - datastoreSegment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - externalSegment := StartExternalSegment(txn, req) - externalSegment.Response = &http.Response{ - Header: outboundCrossProcessResponse(), - } - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret") - externalSegment.End() - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "OtherTransaction/Go/hello", - Root: internal.WantTraceSegment{ - SegmentName: "ROOT", - Attributes: map[string]interface{}{}, - Children: []internal.WantTraceSegment{{ - SegmentName: "OtherTransaction/Go/hello", - Attributes: map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything}, - Children: []internal.WantTraceSegment{ - { - SegmentName: "Custom/basic", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - }, - }, - { - SegmentName: "Datastore/statement/MySQL/mycollection/myoperation", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - }, - }, - { - SegmentName: "ExternalTransaction/example.com/12345#67890/WebTransaction/Go/txn", - Attributes: map[string]interface{}{ - "backtrace": internal.MatchAnything, - "transaction_guid": internal.MatchAnything, - }, - }, - }, - }}, - }, - }}) -} diff --git a/internal_serverless_test.go b/internal_serverless_test.go deleted file mode 100644 index 12079b3fb..000000000 --- a/internal_serverless_test.go +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "bytes" - "strings" - "testing" - "time" - - "github.com/newrelic/go-agent/internal" -) - -func TestServerlessDistributedTracingConfigPresent(t *testing.T) { - cfgFn := func(cfg *Config) { - cfg.ServerlessMode.Enabled = true - cfg.DistributedTracer.Enabled = true - cfg.ServerlessMode.AccountID = "123" - cfg.ServerlessMode.TrustedAccountKey = "trustkey" - cfg.ServerlessMode.PrimaryAppID = "456" - } - app := testApp(nil, cfgFn, t) - payload := app.StartTransaction("hello", nil, nil).CreateDistributedTracePayload() - txn := app.StartTransaction("hello", nil, nil) - txn.AcceptDistributedTracePayload(TransportHTTP, payload) - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: singleCount}, - }) -} - -func TestServerlessDistributedTracingConfigPartiallyPresent(t *testing.T) { - // This tests that if ServerlessMode.PrimaryAppID is unset it should - // default to "Unknown". - cfgFn := func(cfg *Config) { - cfg.ServerlessMode.Enabled = true - cfg.DistributedTracer.Enabled = true - cfg.ServerlessMode.AccountID = "123" - cfg.ServerlessMode.TrustedAccountKey = "trustkey" - } - app := testApp(nil, cfgFn, t) - payload := app.StartTransaction("hello", nil, nil).CreateDistributedTracePayload() - txn := app.StartTransaction("hello", nil, nil) - txn.AcceptDistributedTracePayload(TransportHTTP, payload) - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/App/123/Unknown/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/Unknown/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/Unknown/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/Unknown/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: singleCount}, - }) -} - -func TestServerlessDistributedTracingConfigTrustKeyAbsent(t *testing.T) { - // Test that distributed tracing works if only AccountID has been set. - cfgFn := func(cfg *Config) { - cfg.ServerlessMode.Enabled = true - cfg.DistributedTracer.Enabled = true - cfg.ServerlessMode.AccountID = "123" - } - app := testApp(nil, cfgFn, t) - payload := app.StartTransaction("hello", nil, nil).CreateDistributedTracePayload() - txn := app.StartTransaction("hello", nil, nil) - txn.AcceptDistributedTracePayload(TransportHTTP, payload) - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/App/123/Unknown/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/Unknown/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/Unknown/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/Unknown/HTTP/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: singleCount}, - }) -} - -func TestServerlessDistributedTracingConfigAbsent(t *testing.T) { - // Test that payloads do not get created or accepted when distributed - // tracing configuration is not present. - cfgFn := func(cfg *Config) { - cfg.ServerlessMode.Enabled = true - cfg.DistributedTracer.Enabled = true - } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - payload := txn.CreateDistributedTracePayload() - if "" != payload.Text() { - t.Error(payload.Text()) - } - nonemptyPayload := func() DistributedTracePayload { - app := testApp(nil, func(cfg *Config) { - cfgFn(cfg) - cfg.ServerlessMode.AccountID = "123" - cfg.ServerlessMode.TrustedAccountKey = "trustkey" - cfg.ServerlessMode.PrimaryAppID = "456" - }, t) - return app.StartTransaction("hello", nil, nil).CreateDistributedTracePayload() - }() - if "" == nonemptyPayload.Text() { - t.Error(nonemptyPayload.Text()) - } - err := txn.AcceptDistributedTracePayload(TransportHTTP, nonemptyPayload) - if err != nil { - t.Error(err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - }) -} - -func TestServerlessLowApdex(t *testing.T) { - apdex := -1 * time.Second - cfgFn := func(cfg *Config) { - cfg.ServerlessMode.Enabled = true - cfg.ServerlessMode.ApdexThreshold = apdex - } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - txn.SetWebRequest(nil) // only web gets apdex - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - // third apdex field is failed count - {Name: "Apdex", Scope: "", Forced: true, Data: []float64{0, 0, 1, apdex.Seconds(), apdex.Seconds(), 0}}, - {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: []float64{0, 0, 1, apdex.Seconds(), apdex.Seconds(), 0}}, - }) -} - -func TestServerlessHighApdex(t *testing.T) { - apdex := 1 * time.Hour - cfgFn := func(cfg *Config) { - cfg.ServerlessMode.Enabled = true - cfg.ServerlessMode.ApdexThreshold = apdex - } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - txn.SetWebRequest(nil) // only web gets apdex - txn.End() - - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - // first apdex field is satisfied count - {Name: "Apdex", Scope: "", Forced: true, Data: []float64{1, 0, 0, apdex.Seconds(), apdex.Seconds(), 0}}, - {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: []float64{1, 0, 0, apdex.Seconds(), apdex.Seconds(), 0}}, - }) -} - -func TestServerlessRecordCustomMetric(t *testing.T) { - cfgFn := func(cfg *Config) { cfg.ServerlessMode.Enabled = true } - app := testApp(nil, cfgFn, t) - err := app.RecordCustomMetric("myMetric", 123.0) - if err != errMetricServerless { - t.Error(err) - } -} - -func TestServerlessRecordCustomEvent(t *testing.T) { - cfgFn := func(cfg *Config) { cfg.ServerlessMode.Enabled = true } - app := testApp(nil, cfgFn, t) - - attributes := map[string]interface{}{"zip": 1} - err := app.RecordCustomEvent("myType", attributes) - if err != nil { - t.Error(err) - } - app.ExpectCustomEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "type": "myType", - "timestamp": internal.MatchAnything, - }, - UserAttributes: attributes, - }}) - - buf := &bytes.Buffer{} - internal.ServerlessWrite(app, "my-arn", buf) - - _, data, err := internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Fatal(err) - } - - // Data should contain only custom events. Dynamic timestamp makes exact - // comparison difficult. - eventData := string(data["custom_event_data"]) - if !strings.Contains(eventData, `{"zip":1}`) { - t.Error(eventData) - } - if len(data) != 1 { - t.Fatal(data) - } -} - -func TestServerlessJSON(t *testing.T) { - cfgFn := func(cfg *Config) { - cfg.ServerlessMode.Enabled = true - } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, nil) - txn.(internal.AddAgentAttributer).AddAgentAttribute(internal.AttributeAWSLambdaARN, "thearn", nil) - txn.End() - - buf := &bytes.Buffer{} - internal.ServerlessWrite(app, "lambda-test-arn", buf) - - metadata, data, err := internal.ParseServerlessPayload(buf.Bytes()) - if err != nil { - t.Fatal(err) - } - - // Data should contain txn event and metrics. Timestamps make exact - // JSON comparison tough. - if v := data["metric_data"]; nil == v { - t.Fatal(data) - } - if v := data["analytic_event_data"]; nil == v { - t.Fatal(data) - } - if v := string(metadata["arn"]); v != `"lambda-test-arn"` { - t.Fatal(v) - } - if v := string(metadata["agent_version"]); v != `"`+Version+`"` { - t.Fatal(v) - } -} - -func validSampler(s internal.AdaptiveSampler) bool { - _, isSampleEverything := s.(internal.SampleEverything) - _, isSampleNothing := s.(internal.SampleEverything) - return (nil != s) && !isSampleEverything && !isSampleNothing -} - -func TestServerlessConnectReply(t *testing.T) { - cfg := NewConfig("", "") - cfg.ServerlessMode.ApdexThreshold = 2 * time.Second - cfg.ServerlessMode.AccountID = "the-account-id" - cfg.ServerlessMode.TrustedAccountKey = "the-trust-key" - cfg.ServerlessMode.PrimaryAppID = "the-primary-app" - reply := newServerlessConnectReply(cfg) - if reply.ApdexThresholdSeconds != 2 { - t.Error(reply.ApdexThresholdSeconds) - } - if reply.AccountID != "the-account-id" { - t.Error(reply.AccountID) - } - if reply.TrustedAccountKey != "the-trust-key" { - t.Error(reply.TrustedAccountKey) - } - if reply.PrimaryAppID != "the-primary-app" { - t.Error(reply.PrimaryAppID) - } - if !validSampler(reply.AdaptiveSampler) { - t.Error(reply.AdaptiveSampler) - } - - // Now test the defaults: - cfg = NewConfig("", "") - reply = newServerlessConnectReply(cfg) - if reply.ApdexThresholdSeconds != 0.5 { - t.Error(reply.ApdexThresholdSeconds) - } - if reply.AccountID != "" { - t.Error(reply.AccountID) - } - if reply.TrustedAccountKey != "" { - t.Error(reply.TrustedAccountKey) - } - if reply.PrimaryAppID != "Unknown" { - t.Error(reply.PrimaryAppID) - } - if !validSampler(reply.AdaptiveSampler) { - t.Error(reply.AdaptiveSampler) - } -} diff --git a/internal_set_web_request_test.go b/internal_set_web_request_test.go deleted file mode 100644 index 2373f3fba..000000000 --- a/internal_set_web_request_test.go +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "net/http" - "net/url" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -type customRequest struct { - header http.Header - u *url.URL - method string - transport TransportType -} - -func (r customRequest) Header() http.Header { return r.header } -func (r customRequest) URL() *url.URL { return r.u } -func (r customRequest) Method() string { return r.method } -func (r customRequest) Transport() TransportType { return r.transport } - -var ( - sampleHTTPRequest = func() *http.Request { - req, err := http.NewRequest("GET", "http://www.newrelic.com", nil) - if nil != err { - panic(err) - } - req.Header.Set("Accept", "myaccept") - req.Header.Set("Content-Type", "mycontent") - req.Header.Set("Host", "myhost") - req.Header.Set("Content-Length", "123") - return req - }() - sampleCustomRequest = func() customRequest { - u, err := url.Parse("http://www.newrelic.com") - if nil != err { - panic(err) - } - hdr := make(http.Header) - hdr.Set("Accept", "myaccept") - hdr.Set("Content-Type", "mycontent") - hdr.Set("Host", "myhost") - hdr.Set("Content-Length", "123") - return customRequest{ - header: hdr, - u: u, - method: "GET", - transport: TransportHTTP, - } - }() - sampleRequestAgentAttributes = map[string]interface{}{ - AttributeRequestMethod: "GET", - AttributeRequestAccept: "myaccept", - AttributeRequestContentType: "mycontent", - AttributeRequestContentLength: 123, - AttributeRequestHost: "myhost", - AttributeRequestURI: "http://www.newrelic.com", - } -) - -func TestSetWebRequestNil(t *testing.T) { - // Test that using SetWebRequest with nil marks the transaction as a web - // transaction. - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.SetWebRequest(nil) - if err != nil { - t.Error("unexpected error", err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "traceId": internal.MatchAnything, - "nr.apdexPerfZone": internal.MatchAnything, - }, - }}) -} - -func TestSetWebRequestNilPointer(t *testing.T) { - // Test that calling NewWebRequest with a nil pointer is safe and - // returns a nil interface that SetWebRequest handles safely. - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.SetWebRequest(NewWebRequest(nil)) - if err != nil { - t.Error("unexpected error", err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "traceId": internal.MatchAnything, - "nr.apdexPerfZone": internal.MatchAnything, - }, - }}) -} - -func TestSetWebRequestHTTPRequest(t *testing.T) { - // Test that NewWebRequest correctly turns an *http.Request into a - // WebRequest that SetWebRequest uses as expected. - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.SetWebRequest(NewWebRequest(sampleHTTPRequest)) - if err != nil { - t.Error("unexpected error", err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: sampleRequestAgentAttributes, - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "traceId": internal.MatchAnything, - "nr.apdexPerfZone": internal.MatchAnything, - }, - }}) -} - -func TestSetWebRequestCustomRequest(t *testing.T) { - // Test that a custom type which implements WebRequest is used by - // SetWebRequest as expected. - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.SetWebRequest(sampleCustomRequest) - if err != nil { - t.Error("unexpected error", err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: sampleRequestAgentAttributes, - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "traceId": internal.MatchAnything, - "nr.apdexPerfZone": internal.MatchAnything, - }, - }}) -} - -func TestSetWebRequestAlreadyEnded(t *testing.T) { - // Test that SetWebRequest returns an error if called after - // Transaction.End. - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - txn.End() - err := txn.SetWebRequest(sampleCustomRequest) - if err != errAlreadyEnded { - t.Error("incorrect error", err) - } - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "traceId": internal.MatchAnything, - }, - }}) -} - -func TestSetWebRequestWithDistributedTracing(t *testing.T) { - // Test that the WebRequest.Transport() return value is used as the - // distributed tracing transport if a distributed tracing header is - // found in the WebRequest.Header(). - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - payload := makePayload(app, nil) - // Copy sampleCustomRequest to avoid modifying it since it is used in - // other tests. - req := sampleCustomRequest - req.header = map[string][]string{ - DistributedTracePayloadHeader: {payload.Text()}, - } - txn := app.StartTransaction("hello", nil, nil) - err := txn.SetWebRequest(req) - if nil != err { - t.Error("unexpected error", err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/all", Scope: "", Forced: false, Data: nil}, - {Name: "TransportDuration/App/123/456/HTTP/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "Supportability/DistributedTrace/AcceptPayload/Success", Scope: "", Forced: true, Data: singleCount}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: map[string]interface{}{ - "request.method": "GET", - "request.uri": "http://www.newrelic.com", - }, - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "parent.type": "App", - "parent.account": "123", - "parent.app": "456", - "parent.transportType": "HTTP", - "parent.transportDuration": internal.MatchAnything, - "parentId": internal.MatchAnything, - "traceId": internal.MatchAnything, - "parentSpanId": internal.MatchAnything, - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "nr.apdexPerfZone": internal.MatchAnything, - }, - }}) -} - -type incompleteRequest struct{} - -func (r incompleteRequest) Header() http.Header { return nil } -func (r incompleteRequest) URL() *url.URL { return nil } -func (r incompleteRequest) Method() string { return "" } -func (r incompleteRequest) Transport() TransportType { return TransportUnknown } - -func TestSetWebRequestIncompleteRequest(t *testing.T) { - // Test SetWebRequest will safely handle situations where the request's - // URL() and Header() methods return nil. - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.SetWebRequest(incompleteRequest{}) - if err != nil { - t.Error("unexpected error", err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - }) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: map[string]interface{}{}, - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "guid": internal.MatchAnything, - "sampled": internal.MatchAnything, - "priority": internal.MatchAnything, - "traceId": internal.MatchAnything, - "nr.apdexPerfZone": internal.MatchAnything, - }, - }}) -} diff --git a/internal_set_web_response_test.go b/internal_set_web_response_test.go deleted file mode 100644 index 532e9edd8..000000000 --- a/internal_set_web_response_test.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -func TestTransactionStartedWithoutResponse(t *testing.T) { - // Test that the http.ResponseWriter methods of the transaction can - // safely be called if a ResponseWriter is not provided. - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - txn.WriteHeader(123) - if hdr := txn.Header(); hdr != nil { - t.Error(hdr) - } - n, err := txn.Write([]byte("should not panic")) - if err != nil || n != 0 { - t.Error(err, n) - } - txn.End() - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: map[string]interface{}{"httpResponseCode": 123}, - Intrinsics: map[string]interface{}{"name": "OtherTransaction/Go/hello"}, - }}) -} - -func TestSetWebResponseNil(t *testing.T) { - // Test that the http.ResponseWriter methods of the transaction can - // safely be called if txn.SetWebResponse(nil) has been called. - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - txn = txn.SetWebResponse(nil) - txn.WriteHeader(123) - if hdr := txn.Header(); hdr != nil { - t.Error(hdr) - } - n, err := txn.Write([]byte("should not panic")) - if err != nil || n != 0 { - t.Error(err, n) - } - txn.End() - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: map[string]interface{}{"httpResponseCode": 123}, - Intrinsics: map[string]interface{}{"name": "OtherTransaction/Go/hello"}, - }}) -} - -func TestSetWebResponseSuccess(t *testing.T) { - // Test that the http.ResponseWriter methods of the transaction use the - // writer set by SetWebResponse. - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - w := httptest.NewRecorder() - txn = txn.SetWebResponse(w) - txn.WriteHeader(123) - hdr := txn.Header() - hdr.Set("zip", "zap") - body := "should not panic" - n, err := txn.Write([]byte(body)) - if err != nil || n != len(body) { - t.Error(err, n) - } - txn.End() - if w.Code != 123 { - t.Error(w.Code) - } - if w.HeaderMap.Get("zip") != "zap" { - t.Error(w.HeaderMap) - } - if w.Body.String() != body { - t.Error(w.Body.String()) - } - app.ExpectTxnEvents(t, []internal.WantEvent{{ - AgentAttributes: map[string]interface{}{"httpResponseCode": 123}, - Intrinsics: map[string]interface{}{"name": "OtherTransaction/Go/hello"}, - }}) -} - -type writerWithFlush struct{} - -func (w writerWithFlush) Header() http.Header { return nil } -func (w writerWithFlush) WriteHeader(int) {} -func (w writerWithFlush) Write([]byte) (int, error) { return 0, nil } -func (w writerWithFlush) Flush() {} - -func TestSetWebResponseTxnUpgraded(t *testing.T) { - // Test that the using Transaction reference returned by SetWebResponse - // properly has the optional methods that the ResponseWriter does. - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - if _, ok := txn.(http.Flusher); ok { - t.Error("should not have Flusher") - } - txn = txn.SetWebResponse(writerWithFlush{}) - if _, ok := txn.(http.Flusher); !ok { - t.Error("should have Flusher now") - } -} diff --git a/internal_slow_queries_test.go b/internal_slow_queries_test.go deleted file mode 100644 index dd1d42ca8..000000000 --- a/internal_slow_queries_test.go +++ /dev/null @@ -1,851 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "strings" - "testing" - "time" - - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/crossagent" -) - -func TestSlowQueryBasic(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - }}) -} - -func TestSlowQueryLocallyDisabled(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - cfg.DatastoreTracer.SlowQuery.Enabled = false - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{}) -} - -func TestSlowQueryRemotelyDisabled(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - replyfn := func(reply *internal.ConnectReply) { - reply.CollectTraces = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{}) -} - -func TestSlowQueryBelowThreshold(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 1 * time.Hour - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{}) -} - -func TestSlowQueryDatabaseProvided(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - DatabaseName: "my_database", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "my_database", - Host: "", - PortPathOrID: "", - }}) -} - -func TestSlowQueryHostProvided(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - Host: "db-server-1", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "db-server-1", - PortPathOrID: "unknown", - }}) - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/MySQL/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: scope, Forced: false, Data: nil}, - {Name: "Datastore/instance/MySQL/db-server-1/unknown", Scope: "", Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestSlowQueryPortProvided(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - PortPathOrID: "98021", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "unknown", - PortPathOrID: "98021", - }}) - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/MySQL/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: scope, Forced: false, Data: nil}, - {Name: "Datastore/instance/MySQL/unknown/98021", Scope: "", Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestSlowQueryHostPortProvided(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - Host: "db-server-1", - PortPathOrID: "98021", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "db-server-1", - PortPathOrID: "98021", - }}) - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/MySQL/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: scope, Forced: false, Data: nil}, - {Name: "Datastore/instance/MySQL/db-server-1/98021", Scope: "", Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestSlowQueryAggregation(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - ds := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - } - ds.End() - ds = DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - } - ds.End() - ds = DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastorePostgres, - Collection: "products", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO products (name, price) VALUES ($1, $2)", - } - ds.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 2, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - }, { - Count: 1, - MetricName: "Datastore/statement/Postgres/products/INSERT", - Query: "INSERT INTO products (name, price) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - }, - }) -} - -func TestSlowQueryMissingQuery(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "'INSERT' on 'users' using 'MySQL'", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - }}) -} - -func TestSlowQueryMissingEverything(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/operation/Unknown/other", - Query: "'other' on 'unknown' using 'Unknown'", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - }}) - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/Unknown/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/Unknown/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/Unknown/other", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/operation/Unknown/other", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestSlowQueryWithQueryParameters(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - params := map[string]interface{}{ - "str": "zap", - "int": 123, - } - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - QueryParameters: params, - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - Params: params, - }}) -} - -func TestSlowQueryHighSecurity(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - cfg.HighSecurity = true - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - params := map[string]interface{}{ - "str": "zap", - "int": 123, - } - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - QueryParameters: params, - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - Params: nil, - }}) -} - -func TestSlowQuerySecurityPolicyFalse(t *testing.T) { - // When the record_sql security policy is set to false, sql parameters - // and the sql format string should be replaced. - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - replyfn := func(reply *internal.ConnectReply) { - reply.SecurityPolicies.RecordSQL.SetEnabled(false) - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - params := map[string]interface{}{ - "str": "zap", - "int": 123, - } - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - QueryParameters: params, - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "'INSERT' on 'users' using 'MySQL'", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - Params: nil, - }}) -} - -func TestSlowQuerySecurityPolicyTrue(t *testing.T) { - // When the record_sql security policy is set to true, sql parameters - // should be omitted. - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - replyfn := func(reply *internal.ConnectReply) { - reply.SecurityPolicies.RecordSQL.SetEnabled(true) - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - params := map[string]interface{}{ - "str": "zap", - "int": 123, - } - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - QueryParameters: params, - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - Params: nil, - }}) -} - -func TestSlowQueryInvalidParameters(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - params := map[string]interface{}{ - "str": "zap", - "int": 123, - "invalid_value": struct{}{}, - strings.Repeat("key-too-long", 100): 1, - "long-key": strings.Repeat("A", 300), - } - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - QueryParameters: params, - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - Params: map[string]interface{}{ - "str": "zap", - "int": 123, - "long-key": strings.Repeat("A", 255), - }, - }}) -} - -func TestSlowQueryParametersDisabled(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - cfg.DatastoreTracer.QueryParameters.Enabled = false - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - params := map[string]interface{}{ - "str": "zap", - "int": 123, - } - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - QueryParameters: params, - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - Params: nil, - }}) -} - -func TestSlowQueryInstanceDisabled(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - cfg.DatastoreTracer.InstanceReporting.Enabled = false - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - Host: "db-server-1", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - }}) - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/MySQL/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestSlowQueryInstanceDisabledLocalhost(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - cfg.DatastoreTracer.InstanceReporting.Enabled = false - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - Host: "localhost", - PortPathOrID: "3306", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - }}) - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/MySQL/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/users/INSERT", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestSlowQueryDatabaseNameDisabled(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - cfg.DatastoreTracer.DatabaseNameReporting.Enabled = false - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - DatabaseName: "db-server-1", - } - s1.End() - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - }}) -} - -func TestDatastoreAPICrossAgent(t *testing.T) { - var testcases []struct { - TestName string `json:"test_name"` - Input struct { - Parameters struct { - Product string `json:"product"` - Collection string `json:"collection"` - Operation string `json:"operation"` - Host string `json:"host"` - PortPathOrID string `json:"port_path_or_id"` - DatabaseName string `json:"database_name"` - } `json:"parameters"` - IsWeb bool `json:"is_web"` - SystemHostname string `json:"system_hostname"` - Configuration struct { - InstanceEnabled bool `json:"datastore_tracer.instance_reporting.enabled"` - DatabaseEnabled bool `json:"datastore_tracer.database_name_reporting.enabled"` - } - } - Expectation struct { - MetricsScoped []string `json:"metrics_scoped"` - MetricsUnscoped []string `json:"metrics_unscoped"` - Trace struct { - MetricName string `json:"metric_name"` - Host string `json:"host"` - PortPathOrID string `json:"port_path_or_id"` - DatabaseName string `json:"database_name"` - } `json:"transaction_segment_and_slow_query_trace"` - } - } - - err := crossagent.ReadJSON("datastores/datastore_api.json", &testcases) - if err != nil { - t.Fatal(err) - } - - for _, tc := range testcases { - query := "my query" - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - cfg.DatastoreTracer.InstanceReporting.Enabled = - tc.Input.Configuration.InstanceEnabled - cfg.DatastoreTracer.DatabaseNameReporting.Enabled = - tc.Input.Configuration.DatabaseEnabled - } - app := testApp(nil, cfgfn, t) - var txn Transaction - var txnURL string - if tc.Input.IsWeb { - txnURL = helloPath - txn = app.StartTransaction("hello", nil, helloRequest) - } else { - txn = app.StartTransaction("hello", nil, nil) - } - ds := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreProduct(tc.Input.Parameters.Product), - Operation: tc.Input.Parameters.Operation, - Collection: tc.Input.Parameters.Collection, - PortPathOrID: tc.Input.Parameters.PortPathOrID, - Host: tc.Input.Parameters.Host, - DatabaseName: tc.Input.Parameters.DatabaseName, - ParameterizedQuery: query, - } - ds.End() - txn.End() - - var metrics []internal.WantMetric - var scope string - if tc.Input.IsWeb { - scope = "WebTransaction/Go/hello" - metrics = append([]internal.WantMetric{}, webMetrics...) - } else { - scope = "OtherTransaction/Go/hello" - metrics = append([]internal.WantMetric{}, backgroundMetrics...) - } - - for _, m := range tc.Expectation.MetricsScoped { - metrics = append(metrics, internal.WantMetric{ - Name: m, Scope: scope, Forced: nil, Data: nil, - }) - } - for _, m := range tc.Expectation.MetricsUnscoped { - metrics = append(metrics, internal.WantMetric{ - Name: m, Scope: "", Forced: nil, Data: nil, - }) - } - - expectTraceHost := tc.Expectation.Trace.Host - if tc.Input.SystemHostname != "" { - for i := range metrics { - metrics[i].Name = strings.Replace(metrics[i].Name, - tc.Input.SystemHostname, - internal.ThisHost, -1) - } - expectTraceHost = strings.Replace(expectTraceHost, - tc.Input.SystemHostname, - internal.ThisHost, -1) - } - - tt := internal.ExtendValidator(t, tc.TestName) - app.ExpectMetrics(tt, metrics) - app.ExpectSlowQueries(tt, []internal.WantSlowQuery{{ - Count: 1, - MetricName: tc.Expectation.Trace.MetricName, - TxnName: scope, - DatabaseName: tc.Expectation.Trace.DatabaseName, - Host: expectTraceHost, - PortPathOrID: tc.Expectation.Trace.PortPathOrID, - TxnURL: txnURL, - Query: query, - }}) - } -} - -func TestSlowQueryParamsInvalid(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.DatastoreTracer.SlowQuery.Threshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "users", - Operation: "INSERT", - ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", - QueryParameters: map[string]interface{}{ - "cookies": []string{"chocolate", "sugar", "oatmeal"}, - "number": 5, - }, - } - err := s1.End() - if nil == err { - t.Error("error should have been returned") - } - txn.End() - - app.ExpectSlowQueries(t, []internal.WantSlowQuery{{ - Count: 1, - MetricName: "Datastore/statement/MySQL/users/INSERT", - Query: "INSERT INTO users (name, age) VALUES ($1, $2)", - TxnName: "WebTransaction/Go/hello", - TxnURL: "/hello", - DatabaseName: "", - Host: "", - PortPathOrID: "", - Params: map[string]interface{}{"number": 5}, - }}) -} diff --git a/internal_span_events_test.go b/internal_span_events_test.go deleted file mode 100644 index e571ba20c..000000000 --- a/internal_span_events_test.go +++ /dev/null @@ -1,591 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "net/http" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -func TestSpanEventSuccess(t *testing.T) { - // Test that a basic segment creates a span event, and that a - // transaction has a root span event. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - segment := StartSegment(txn, "mySegment") - segment.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "priority": internal.MatchAnything, - "guid": "0e97aeb2f79d5d27", - "transactionId": "d9466896a525ccbf", - "nr.entryPoint": true, - "traceId": "d9466896a525ccbf", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "name": "Custom/mySegment", - "sampled": true, - "category": "generic", - "priority": internal.MatchAnything, - "guid": "bcfb32e050b264b8", - "transactionId": "d9466896a525ccbf", - "traceId": "d9466896a525ccbf", - "parentId": "0e97aeb2f79d5d27", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestSpanEventsLocallyDisabled(t *testing.T) { - // Test that span events do not get created if Config.SpanEvents.Enabled - // is false. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Enabled = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - segment := StartSegment(txn, "mySegment") - segment.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{}) -} - -func TestSpanEventsRemotelyDisabled(t *testing.T) { - // Test that span events do not get created if the connect reply - // disables span events. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.CollectSpanEvents = false - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - segment := StartSegment(txn, "mySegment") - segment.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{}) -} - -func TestSpanEventsDisabledWithoutDistributedTracing(t *testing.T) { - // Test that span events do not get created distributed tracing is not - // enabled. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - segment := StartSegment(txn, "mySegment") - segment.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{}) -} - -func TestSpanEventDatastoreExternal(t *testing.T) { - // Test that a datastore and external segments creates the correct span - // events. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - segment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - } - segment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - s := StartExternalSegment(txn, req) - s.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "sampled": true, - "name": "Datastore/statement/MySQL/mycollection/myoperation", - "category": "datastore", - "component": "MySQL", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "db.statement": "myquery", - "db.instance": "dbname", - "db.collection": "mycollection", - "peer.address": "myhost:myport", - "peer.hostname": "myhost", - }, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "name": "External/example.com/http/GET", - "category": "http", - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "http.url": "http://example.com", - "http.method": "GET", - }, - }, - }) -} - -func TestSpanEventAttributesDisabled(t *testing.T) { - // Test that SpanEvents.Attributes.Enabled correctly disables span - // attributes. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Attributes.Enabled = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - segment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - } - segment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - s := StartExternalSegment(txn, req) - s.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "sampled": true, - "name": "Datastore/statement/MySQL/mycollection/myoperation", - "category": "datastore", - "component": "MySQL", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "name": "External/example.com/http/GET", - "category": "http", - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestSpanEventAttributesSpecificallyExcluded(t *testing.T) { - // Test that SpanEvents.Attributes.Exclude excludes span attributes. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Attributes.Exclude = []string{ - SpanAttributeDBStatement, - SpanAttributeDBInstance, - SpanAttributeDBCollection, - SpanAttributePeerAddress, - SpanAttributePeerHostname, - SpanAttributeHTTPURL, - SpanAttributeHTTPMethod, - } - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - segment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - } - segment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - s := StartExternalSegment(txn, req) - s.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "sampled": true, - "name": "Datastore/statement/MySQL/mycollection/myoperation", - "category": "datastore", - "component": "MySQL", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "name": "External/example.com/http/GET", - "category": "http", - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestSpanEventAttributesExcluded(t *testing.T) { - // Test that Attributes.Exclude excludes span attributes. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.Attributes.Exclude = []string{ - SpanAttributeDBStatement, - SpanAttributeDBInstance, - SpanAttributeDBCollection, - SpanAttributePeerAddress, - SpanAttributePeerHostname, - SpanAttributeHTTPURL, - SpanAttributeHTTPMethod, - } - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - segment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - } - segment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - s := StartExternalSegment(txn, req) - s.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "sampled": true, - "name": "Datastore/statement/MySQL/mycollection/myoperation", - "category": "datastore", - "component": "MySQL", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "name": "External/example.com/http/GET", - "category": "http", - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestSpanEventAttributesLASP(t *testing.T) { - // Test that security policies prevent the capture of the input query - // statement. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.SecurityPolicies.RecordSQL.SetEnabled(false) - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - segment := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "mycollection", - Operation: "myoperation", - ParameterizedQuery: "myquery", - Host: "myhost", - PortPathOrID: "myport", - DatabaseName: "dbname", - } - segment.End() - req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil) - s := StartExternalSegment(txn, req) - s.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "sampled": true, - "name": "Datastore/statement/MySQL/mycollection/myoperation", - "category": "datastore", - "component": "MySQL", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "db.instance": "dbname", - "db.collection": "mycollection", - "peer.address": "myhost:myport", - "peer.hostname": "myhost", - "db.statement": "'myoperation' on 'mycollection' using 'MySQL'", - }, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "name": "External/example.com/http/GET", - "category": "http", - "component": "http", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "http.url": "http://example.com", - "http.method": "GET", - }, - }, - }) -} - -func TestAddAgentSpanAttribute(t *testing.T) { - // Test that AddAgentSpanAttribute successfully adds attributes to - // spans. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - s := StartSegment(txn, "hi") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret") - s.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "name": "Custom/hi", - "sampled": true, - "category": "generic", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - "aws.operation": "secret", - "aws.requestId": "123", - "aws.region": "west", - }, - }, - }) -} - -func TestAddAgentSpanAttributeExcluded(t *testing.T) { - // Test that span attributes added by AddAgentSpanAttribute are subject - // to span attribute configuration. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Attributes.Exclude = []string{ - SpanAttributeAWSOperation, - SpanAttributeAWSRequestID, - SpanAttributeAWSRegion, - } - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - s := StartSegment(txn, "hi") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret") - s.End() - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "name": "Custom/hi", - "sampled": true, - "category": "generic", - "parentId": internal.MatchAnything, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestAddSpanAttributeNoActiveSpan(t *testing.T) { - // Test that AddAgentSpanAttribute does not have problems if called when - // there is no active span. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - // Do not panic if there are no active spans! - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123") - internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret") - txn.End() - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestAddSpanAttributeNilTransaction(t *testing.T) { - // Test that AddAgentSpanAttribute does not panic if the transaction is - // nil. - internal.AddAgentSpanAttribute(nil, internal.SpanAttributeAWSRegion, "west") - internal.AddAgentSpanAttribute(nil, internal.SpanAttributeAWSRequestID, "123") - internal.AddAgentSpanAttribute(nil, internal.SpanAttributeAWSOperation, "secret") -} diff --git a/internal_synthetics_test.go b/internal_synthetics_test.go deleted file mode 100644 index 60c8d4cb0..000000000 --- a/internal_synthetics_test.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "net/http" - "testing" - - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/cat" -) - -// This collection of top-level tests affirms, for all possible combinations of -// Old CAT, BetterCAT, and Synthetics, that when an inbound request contains a -// synthetics header, the subsequent outbound request propagates that synthetics -// header. Synthetics uses an obfuscated JSON header, so this test requires a -// really particular set of values, e.g. rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr. - -var ( - trustedAccounts = func() map[int]struct{} { - ta := make(map[int]struct{}) - ta[1] = struct{}{} // Trust account 1, from syntheticsConnectReplyFn. - ta[444] = struct{}{} // Trust account 444, from syntheticsHeader. - return ta - }() - - syntheticsConnectReplyFn = func(reply *internal.ConnectReply) { - reply.EncodingKey = "1234567890123456789012345678901234567890" - reply.CrossProcessID = "1#1" - reply.TrustedAccounts = trustedAccounts - } -) - -func inboundSyntheticsRequestBuilder(oldCatEnabled bool, betterCatEnabled bool) *http.Request { - cfgFn := func(cfg *Config) { - cfg.CrossApplicationTracer.Enabled = oldCatEnabled - cfg.DistributedTracer.Enabled = betterCatEnabled - } - app := testApp(syntheticsConnectReplyFn, cfgFn, nil) - txn := app.StartTransaction("requester", nil, nil) - req, err := http.NewRequest("GET", "newrelic.com", nil) - if nil != err { - panic(err) - } - - req.Header.Add( - "X-NewRelic-Synthetics", - "agMfAAECGxpLQkNAQUZHG0VKS0IcAwEHARtFSktCHEBBRkdERUpLQkNAQRYZFF1SU1pbWFkZX1xdUhQBAwEHGV9cXVIUWltYWV5fXF1SU1pbEB8WWFtaVVRdXB9eWVhbGgkLAwUfXllYWxpVVF1cX15ZWFtaVVQSbA==") - - StartExternalSegment(txn, req) - - if betterCatEnabled || !oldCatEnabled { - if cat.NewRelicIDName == req.Header.Get(cat.NewRelicIDName) { - panic("Header contains old cat header NewRelicIDName: " + req.Header.Get(cat.NewRelicIDName)) - } - if cat.NewRelicTxnName == req.Header.Get(cat.NewRelicTxnName) { - panic("Header contains old cat header NewRelicTxnName: " + req.Header.Get(cat.NewRelicTxnName)) - } - } - - if oldCatEnabled { - if "" == req.Header.Get(cat.NewRelicIDName) { - panic("Missing old cat header NewRelicIDName: " + req.Header.Get(cat.NewRelicIDName)) - } - if "" == req.Header.Get(cat.NewRelicTxnName) { - panic("Missing old cat header NewRelicTxnName: " + req.Header.Get(cat.NewRelicTxnName)) - } - } - - if "" == req.Header.Get(cat.NewRelicSyntheticsName) { - panic("missing synthetics header NewRelicSyntheticsName: " + req.Header.Get(cat.NewRelicSyntheticsName)) - } - - return req -} - -func TestSyntheticsOldCAT(t *testing.T) { - cfgFn := func(cfg *Config) { cfg.CrossApplicationTracer.Enabled = true } - app := testApp(syntheticsConnectReplyFn, cfgFn, t) - clientTxn := app.StartTransaction( - "helloOldCAT", - nil, - inboundSyntheticsRequestBuilder(true, false)) - - req, err := http.NewRequest("GET", "newrelic.com", nil) - - if nil != err { - panic(err) - } - - StartExternalSegment(clientTxn, req) - clientTxn.End() - - if "" == req.Header.Get(cat.NewRelicSyntheticsName) { - panic("Outbound request missing synthetics header NewRelicSyntheticsName: " + req.Header.Get(cat.NewRelicSyntheticsName)) - } - - expectedIntrinsics := map[string]interface{}{ - "name": "WebTransaction/Go/helloOldCAT", - "client_cross_process_id": "1#1", - "nr.syntheticsResourceId": "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "nr.syntheticsJobId": "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "nr.syntheticsMonitorId": "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm", - "nr.apdexPerfZone": internal.MatchAnything, - "nr.tripId": internal.MatchAnything, - "nr.pathHash": internal.MatchAnything, - "nr.referringPathHash": internal.MatchAnything, - "nr.referringTransactionGuid": internal.MatchAnything, - "nr.guid": internal.MatchAnything, - } - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: expectedIntrinsics, - }}) -} - -func TestSyntheticsBetterCAT(t *testing.T) { - cfgFn := func(cfg *Config) { - cfg.CrossApplicationTracer.Enabled = false - cfg.DistributedTracer.Enabled = true - } - app := testApp(syntheticsConnectReplyFn, cfgFn, t) - clientTxn := app.StartTransaction( - "helloBetterCAT", - nil, - inboundSyntheticsRequestBuilder(false, true)) - - req, err := http.NewRequest("GET", "newrelic.com", nil) - - if nil != err { - panic(err) - } - - StartExternalSegment(clientTxn, req) - clientTxn.End() - - if "" == req.Header.Get(cat.NewRelicSyntheticsName) { - panic("Outbound request missing synthetics header NewRelicSyntheticsName: " + req.Header.Get(cat.NewRelicSyntheticsName)) - } - - expectedIntrinsics := map[string]interface{}{ - "name": "WebTransaction/Go/helloBetterCAT", - "nr.syntheticsResourceId": "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "nr.syntheticsJobId": "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "nr.syntheticsMonitorId": "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm", - "nr.apdexPerfZone": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - "traceId": internal.MatchAnything, - "guid": internal.MatchAnything, - } - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: expectedIntrinsics, - }}) -} - -func TestSyntheticsStandalone(t *testing.T) { - cfgFn := func(cfg *Config) { - cfg.AppName = "syntheticsReceiver" - cfg.CrossApplicationTracer.Enabled = false - } - app := testApp(syntheticsConnectReplyFn, cfgFn, t) - clientTxn := app.StartTransaction( - "helloSynthetics", - nil, - inboundSyntheticsRequestBuilder(false, false)) - - req, err := http.NewRequest("GET", "newrelic.com", nil) - - if nil != err { - panic(err) - } - - StartExternalSegment(clientTxn, req) - clientTxn.End() - - if "" == req.Header.Get(cat.NewRelicSyntheticsName) { - panic("Outbound request missing synthetics header NewRelicSyntheticsName: " + req.Header.Get(cat.NewRelicSyntheticsName)) - } - - expectedIntrinsics := map[string]interface{}{ - "name": "WebTransaction/Go/helloSynthetics", - "nr.syntheticsResourceId": "rrrrrrr-rrrr-1234-rrrr-rrrrrrrrrrrr", - "nr.syntheticsJobId": "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", - "nr.syntheticsMonitorId": "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm", - "nr.apdexPerfZone": internal.MatchAnything, - "nr.guid": internal.MatchAnything, - } - - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: expectedIntrinsics, - }}) -} diff --git a/internal_test.go b/internal_test.go deleted file mode 100644 index 9a879414b..000000000 --- a/internal_test.go +++ /dev/null @@ -1,2179 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "encoding/json" - "errors" - "math" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/newrelic/go-agent/internal" -) - -var ( - singleCount = []float64{1, 0, 0, 0, 0, 0, 0} - webMetrics = []internal.WantMetric{ - {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, - {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex", Scope: "", Forced: true, Data: nil}, - {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: nil}, - } - webErrorMetrics = append([]internal.WantMetric{ - {Name: "Errors/all", Scope: "", Forced: true, Data: singleCount}, - {Name: "Errors/allWeb", Scope: "", Forced: true, Data: singleCount}, - {Name: "Errors/WebTransaction/Go/hello", Scope: "", Forced: true, Data: singleCount}, - }, webMetrics...) - backgroundMetrics = []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - } - backgroundMetricsUnknownCaller = append([]internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - }, backgroundMetrics...) - backgroundErrorMetrics = append([]internal.WantMetric{ - {Name: "Errors/all", Scope: "", Forced: true, Data: singleCount}, - {Name: "Errors/allOther", Scope: "", Forced: true, Data: singleCount}, - {Name: "Errors/OtherTransaction/Go/hello", Scope: "", Forced: true, Data: singleCount}, - }, backgroundMetrics...) -) - -// compatibleResponseRecorder wraps ResponseRecorder to ensure consistent behavior -// between different versions of Go. -// -// Unfortunately, there was a behavior change in go1.6: -// -// "The net/http/httptest package's ResponseRecorder now initializes a default -// Content-Type header using the same content-sniffing algorithm as in -// http.Server." -type compatibleResponseRecorder struct { - *httptest.ResponseRecorder - wroteHeader bool -} - -func newCompatibleResponseRecorder() *compatibleResponseRecorder { - return &compatibleResponseRecorder{ - ResponseRecorder: httptest.NewRecorder(), - } -} - -func (rw *compatibleResponseRecorder) Header() http.Header { - return rw.ResponseRecorder.Header() -} - -func (rw *compatibleResponseRecorder) Write(buf []byte) (int, error) { - if !rw.wroteHeader { - rw.WriteHeader(200) - rw.wroteHeader = true - } - return rw.ResponseRecorder.Write(buf) -} - -func (rw *compatibleResponseRecorder) WriteHeader(code int) { - rw.wroteHeader = true - rw.ResponseRecorder.WriteHeader(code) -} - -var ( - validParams = map[string]interface{}{"zip": 1, "zap": 2} -) - -var ( - helloResponse = []byte("hello") - helloPath = "/hello" - helloQueryParams = "?secret=hideme" - helloRequest = func() *http.Request { - r, err := http.NewRequest("GET", helloPath+helloQueryParams, nil) - if nil != err { - panic(err) - } - - r.Header.Add(`Accept`, `text/plain`) - r.Header.Add(`Content-Type`, `text/html; charset=utf-8`) - r.Header.Add(`Content-Length`, `753`) - r.Header.Add(`Host`, `my_domain.com`) - r.Header.Add(`User-Agent`, `Mozilla/5.0`) - r.Header.Add(`Referer`, `http://en.wikipedia.org/zip?secret=password`) - - return r - }() - helloRequestAttributes = map[string]interface{}{ - "request.uri": "/hello", - "request.headers.host": "my_domain.com", - "request.headers.referer": "http://en.wikipedia.org/zip", - "request.headers.contentLength": 753, - "request.method": "GET", - "request.headers.accept": "text/plain", - "request.headers.User-Agent": "Mozilla/5.0", - "request.headers.contentType": "text/html; charset=utf-8", - } -) - -func TestNewApplicationNil(t *testing.T) { - cfg := NewConfig("appname", "wrong length") - cfg.Enabled = false - app, err := NewApplication(cfg) - if nil == err { - t.Error("error expected when license key is short") - } - if nil != app { - t.Error("app expected to be nil when error is returned") - } -} - -func handler(w http.ResponseWriter, req *http.Request) { - w.Write(helloResponse) -} - -const ( - testLicenseKey = "0123456789012345678901234567890123456789" -) - -type expectApp interface { - internal.Expect - Application -} - -func testApp(replyfn func(*internal.ConnectReply), cfgfn func(*Config), t testing.TB) expectApp { - cfg := NewConfig("my app", testLicenseKey) - - if nil != cfgfn { - cfgfn(&cfg) - } - - // Prevent spawning app goroutines in tests. - if !cfg.ServerlessMode.Enabled { - cfg.Enabled = false - } - - app, err := newApp(cfg) - if nil != err { - t.Fatal(err) - } - - internal.HarvestTesting(app, replyfn) - - return app.(expectApp) -} - -func TestRecordCustomEventSuccess(t *testing.T) { - app := testApp(nil, nil, t) - err := app.RecordCustomEvent("myType", validParams) - if nil != err { - t.Error(err) - } - app.ExpectCustomEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "type": "myType", - "timestamp": internal.MatchAnything, - }, - UserAttributes: validParams, - }}) -} - -func TestRecordCustomEventHighSecurityEnabled(t *testing.T) { - cfgfn := func(cfg *Config) { cfg.HighSecurity = true } - app := testApp(nil, cfgfn, t) - err := app.RecordCustomEvent("myType", validParams) - if err != errHighSecurityEnabled { - t.Error(err) - } - app.ExpectCustomEvents(t, []internal.WantEvent{}) -} - -func TestRecordCustomEventSecurityPolicy(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { reply.SecurityPolicies.CustomEvents.SetEnabled(false) } - app := testApp(replyfn, nil, t) - err := app.RecordCustomEvent("myType", validParams) - if err != errSecurityPolicy { - t.Error(err) - } - app.ExpectCustomEvents(t, []internal.WantEvent{}) -} - -func TestRecordCustomEventEventsDisabled(t *testing.T) { - cfgfn := func(cfg *Config) { cfg.CustomInsightsEvents.Enabled = false } - app := testApp(nil, cfgfn, t) - err := app.RecordCustomEvent("myType", validParams) - if err != errCustomEventsDisabled { - t.Error(err) - } - app.ExpectCustomEvents(t, []internal.WantEvent{}) -} - -func TestRecordCustomEventBadInput(t *testing.T) { - app := testApp(nil, nil, t) - err := app.RecordCustomEvent("????", validParams) - if err != internal.ErrEventTypeRegex { - t.Error(err) - } - app.ExpectCustomEvents(t, []internal.WantEvent{}) -} - -func TestRecordCustomEventRemoteDisable(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { reply.CollectCustomEvents = false } - app := testApp(replyfn, nil, t) - err := app.RecordCustomEvent("myType", validParams) - if err != errCustomEventsRemoteDisabled { - t.Error(err) - } - app.ExpectCustomEvents(t, []internal.WantEvent{}) -} - -func TestRecordCustomMetricSuccess(t *testing.T) { - app := testApp(nil, nil, t) - err := app.RecordCustomMetric("myMetric", 123.0) - if nil != err { - t.Error(err) - } - expectData := []float64{1, 123.0, 123.0, 123.0, 123.0, 123.0 * 123.0} - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "Custom/myMetric", Scope: "", Forced: false, Data: expectData}, - }) -} - -func TestRecordCustomMetricNameEmpty(t *testing.T) { - app := testApp(nil, nil, t) - err := app.RecordCustomMetric("", 123.0) - if err != errMetricNameEmpty { - t.Error(err) - } -} - -func TestRecordCustomMetricNaN(t *testing.T) { - app := testApp(nil, nil, t) - err := app.RecordCustomMetric("myMetric", math.NaN()) - if err != errMetricNaN { - t.Error(err) - } -} - -func TestRecordCustomMetricPositiveInf(t *testing.T) { - app := testApp(nil, nil, t) - err := app.RecordCustomMetric("myMetric", math.Inf(0)) - if err != errMetricInf { - t.Error(err) - } -} - -func TestRecordCustomMetricNegativeInf(t *testing.T) { - app := testApp(nil, nil, t) - err := app.RecordCustomMetric("myMetric", math.Inf(-1)) - if err != errMetricInf { - t.Error(err) - } -} - -type sampleResponseWriter struct { - code int - written int - header http.Header -} - -func (w *sampleResponseWriter) Header() http.Header { return w.header } -func (w *sampleResponseWriter) Write([]byte) (int, error) { return w.written, nil } -func (w *sampleResponseWriter) WriteHeader(x int) { w.code = x } - -func TestTxnResponseWriter(t *testing.T) { - // NOTE: Eventually when the ResponseWriter is instrumented, this test - // should be expanded to make sure that calling ResponseWriter methods - // after the transaction has ended is not problematic. - w := &sampleResponseWriter{ - header: make(http.Header), - } - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", w, nil) - w.header.Add("zip", "zap") - if out := txn.Header(); out.Get("zip") != "zap" { - t.Error(out.Get("zip")) - } - w.written = 123 - if out, _ := txn.Write(nil); out != 123 { - t.Error(out) - } - if txn.WriteHeader(503); w.code != 503 { - t.Error(w.code) - } -} - -func TestTransactionEventWeb(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - err := txn.End() - if nil != err { - t.Error(err) - } - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "S", - }, - }}) -} - -func TestTransactionEventBackground(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.End() - if nil != err { - t.Error(err) - } - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - }, - }}) -} - -func TestTransactionEventLocallyDisabled(t *testing.T) { - cfgFn := func(cfg *Config) { cfg.TransactionEvents.Enabled = false } - app := testApp(nil, cfgFn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - err := txn.End() - if nil != err { - t.Error(err) - } - app.ExpectTxnEvents(t, []internal.WantEvent{}) -} - -func TestTransactionEventRemotelyDisabled(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { reply.CollectAnalyticsEvents = false } - app := testApp(replyfn, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - err := txn.End() - if nil != err { - t.Error(err) - } - app.ExpectTxnEvents(t, []internal.WantEvent{}) -} - -func myErrorHandler(w http.ResponseWriter, req *http.Request) { - w.Write([]byte("my response")) - if txn, ok := w.(Transaction); ok { - txn.NoticeError(myError{}) - } -} - -func TestWrapHandleFunc(t *testing.T) { - app := testApp(nil, nil, t) - mux := http.NewServeMux() - mux.HandleFunc(WrapHandleFunc(app, helloPath, myErrorHandler)) - w := newCompatibleResponseRecorder() - mux.ServeHTTP(w, helloRequest) - - out := w.Body.String() - if "my response" != out { - t.Error(out) - } - - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - }, - AgentAttributes: mergeAttributes(helloRequestAttributes, map[string]interface{}{ - "httpResponseCode": "200", - }), - }}) - app.ExpectMetrics(t, webErrorMetrics) -} - -func TestWrapHandle(t *testing.T) { - app := testApp(nil, nil, t) - mux := http.NewServeMux() - mux.Handle(WrapHandle(app, helloPath, http.HandlerFunc(myErrorHandler))) - w := newCompatibleResponseRecorder() - mux.ServeHTTP(w, helloRequest) - - out := w.Body.String() - if "my response" != out { - t.Error(out) - } - - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - }, - AgentAttributes: mergeAttributes(helloRequestAttributes, map[string]interface{}{ - "httpResponseCode": "200", - }), - }}) - app.ExpectMetrics(t, webErrorMetrics) -} - -func TestWrapHandleNilApp(t *testing.T) { - var app Application - mux := http.NewServeMux() - mux.Handle(WrapHandle(app, helloPath, http.HandlerFunc(myErrorHandler))) - w := newCompatibleResponseRecorder() - mux.ServeHTTP(w, helloRequest) - - out := w.Body.String() - if "my response" != out { - t.Error(out) - } -} - -func TestSetName(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("one", nil, nil) - if err := txn.SetName("hello"); nil != err { - t.Error(err) - } - txn.End() - if err := txn.SetName("three"); err != errAlreadyEnded { - t.Error(err) - } - - app.ExpectMetrics(t, backgroundMetrics) -} - -func deferEndPanic(txn Transaction, panicMe interface{}) (r interface{}) { - defer func() { - r = recover() - }() - - defer txn.End() - - panic(panicMe) -} - -func TestPanicError(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - - e := myError{} - r := deferEndPanic(txn, e) - if r != e { - t.Error("panic not propagated", r) - } - - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: internal.PanicErrorKlass, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": internal.PanicErrorKlass, - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestPanicString(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - - e := "my string" - r := deferEndPanic(txn, e) - if r != e { - t.Error("panic not propagated", r) - } - - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my string", - Klass: internal.PanicErrorKlass, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": internal.PanicErrorKlass, - "error.message": "my string", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestPanicInt(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - - e := 22 - r := deferEndPanic(txn, e) - if r != e { - t.Error("panic not propagated", r) - } - - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "22", - Klass: internal.PanicErrorKlass, - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": internal.PanicErrorKlass, - "error.message": "22", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) -} - -func TestPanicNil(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - - r := deferEndPanic(txn, nil) - if nil != r { - t.Error(r) - } - - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, backgroundMetrics) -} - -func TestResponseCodeError(t *testing.T) { - app := testApp(nil, nil, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, helloRequest) - - txn.WriteHeader(http.StatusBadRequest) // 400 - txn.WriteHeader(http.StatusUnauthorized) // 401 - - txn.End() - - if http.StatusBadRequest != w.Code { - t.Error(w.Code) - } - - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "Bad Request", - Klass: "400", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "400", - "error.message": "Bad Request", - "transactionName": "WebTransaction/Go/hello", - }, - AgentAttributes: mergeAttributes(helloRequestAttributes, map[string]interface{}{ - "httpResponseCode": "400", - }), - }}) - app.ExpectMetrics(t, webErrorMetrics) -} - -func TestResponseCode404Filtered(t *testing.T) { - app := testApp(nil, nil, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, helloRequest) - - txn.WriteHeader(http.StatusNotFound) - - txn.End() - - if http.StatusNotFound != w.Code { - t.Error(w.Code) - } - - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, webMetrics) -} - -func TestResponseCodeCustomFilter(t *testing.T) { - cfgFn := func(cfg *Config) { - cfg.ErrorCollector.IgnoreStatusCodes = - append(cfg.ErrorCollector.IgnoreStatusCodes, 405) - } - app := testApp(nil, cfgFn, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, helloRequest) - - txn.WriteHeader(405) - - txn.End() - - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, webMetrics) -} - -func TestResponseCodeServerSideFilterObserved(t *testing.T) { - // Test that server-side ignore_status_codes are observed. - cfgFn := func(cfg *Config) { - cfg.ErrorCollector.IgnoreStatusCodes = nil - } - replyfn := func(reply *internal.ConnectReply) { - json.Unmarshal([]byte(`{"agent_config":{"error_collector.ignore_status_codes":[405]}}`), reply) - } - app := testApp(replyfn, cfgFn, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, helloRequest) - - txn.WriteHeader(405) - - txn.End() - - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, webMetrics) -} - -func TestResponseCodeServerSideOverwriteLocal(t *testing.T) { - // Test that server-side ignore_status_codes are used in place of local - // Config.ErrorCollector.IgnoreStatusCodes. - cfgFn := func(cfg *Config) { - } - replyfn := func(reply *internal.ConnectReply) { - json.Unmarshal([]byte(`{"agent_config":{"error_collector.ignore_status_codes":[402]}}`), reply) - } - app := testApp(replyfn, cfgFn, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, helloRequest) - - txn.WriteHeader(404) - - txn.End() - - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "Not Found", - Klass: "404", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "404", - "error.message": "Not Found", - "transactionName": "WebTransaction/Go/hello", - }, - AgentAttributes: mergeAttributes(helloRequestAttributes, map[string]interface{}{ - "httpResponseCode": "404", - }), - }}) - app.ExpectMetrics(t, webErrorMetrics) -} - -func TestResponseCodeAfterEnd(t *testing.T) { - app := testApp(nil, nil, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, helloRequest) - - txn.End() - txn.WriteHeader(http.StatusBadRequest) - - if http.StatusBadRequest != w.Code { - t.Error(w.Code) - } - - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, webMetrics) -} - -func TestResponseCodeAfterWrite(t *testing.T) { - app := testApp(nil, nil, t) - w := newCompatibleResponseRecorder() - txn := app.StartTransaction("hello", w, helloRequest) - - txn.Write([]byte("zap")) - txn.WriteHeader(http.StatusBadRequest) - - txn.End() - - if out := w.Body.String(); "zap" != out { - t.Error(out) - } - - if http.StatusOK != w.Code { - t.Error(w.Code) - } - - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, webMetrics) -} - -func TestQueueTime(t *testing.T) { - app := testApp(nil, nil, t) - req, err := http.NewRequest("GET", helloPath+helloQueryParams, nil) - req.Header.Add("X-Queue-Start", "1465793282.12345") - if nil != err { - t.Fatal(err) - } - txn := app.StartTransaction("hello", nil, req) - txn.NoticeError(myError{}) - txn.End() - - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "WebTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - "queueDuration": internal.MatchAnything, - }, - AgentAttributes: map[string]interface{}{ - "request.uri": "/hello", - "request.method": "GET", - }, - }}) - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "WebFrontend/QueueTime", Scope: "", Forced: true, Data: nil}, - }, webErrorMetrics...)) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - "queueDuration": internal.MatchAnything, - }, - AgentAttributes: nil, - }}) -} - -func TestIgnore(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - txn.NoticeError(myError{}) - err := txn.Ignore() - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectErrors(t, []internal.WantError{}) - app.ExpectErrorEvents(t, []internal.WantEvent{}) - app.ExpectMetrics(t, []internal.WantMetric{}) - app.ExpectTxnEvents(t, []internal.WantEvent{}) -} - -func TestIgnoreAlreadyEnded(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - txn.NoticeError(myError{}) - txn.End() - err := txn.Ignore() - if err != errAlreadyEnded { - t.Error(err) - } - app.ExpectErrors(t, []internal.WantError{{ - TxnName: "OtherTransaction/Go/hello", - Msg: "my msg", - Klass: "newrelic.myError", - }}) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - }, - }}) - app.ExpectMetrics(t, backgroundErrorMetrics) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - }, - }}) -} - -func TestExternalSegmentMethod(t *testing.T) { - req, err := http.NewRequest("POST", "http://request.com/", nil) - if err != nil { - t.Fatal(err) - } - responsereq, err := http.NewRequest("POST", "http://response.com/", nil) - if err != nil { - t.Fatal(err) - } - response := &http.Response{Request: responsereq} - - // empty segment - m := externalSegmentMethod(&ExternalSegment{}) - if "" != m { - t.Error(m) - } - - // empty request - m = externalSegmentMethod(&ExternalSegment{ - Request: nil, - }) - if "" != m { - t.Error(m) - } - - // segment containing request and response - m = externalSegmentMethod(&ExternalSegment{ - Request: req, - Response: response, - }) - if "POST" != m { - t.Error(m) - } - - // Procedure field overrides request and response. - m = externalSegmentMethod(&ExternalSegment{ - Procedure: "GET", - Request: req, - Response: response, - }) - if "GET" != m { - t.Error(m) - } - - req, err = http.NewRequest("", "http://request.com/", nil) - if err != nil { - t.Fatal(err) - } - responsereq, err = http.NewRequest("", "http://response.com/", nil) - if err != nil { - t.Fatal(err) - } - response = &http.Response{Request: responsereq} - - // empty string method means a client GET request - m = externalSegmentMethod(&ExternalSegment{ - Request: req, - Response: response, - }) - if "GET" != m { - t.Error(m) - } - -} - -func TestExternalSegmentURL(t *testing.T) { - rawURL := "http://url.com" - req, err := http.NewRequest("GET", "http://request.com/", nil) - if err != nil { - t.Fatal(err) - } - responsereq, err := http.NewRequest("GET", "http://response.com/", nil) - if err != nil { - t.Fatal(err) - } - response := &http.Response{Request: responsereq} - - // empty segment - u, err := externalSegmentURL(&ExternalSegment{}) - host := internal.HostFromURL(u) - if nil != err || nil != u || "" != host { - t.Error(u, err, internal.HostFromURL(u)) - } - // segment only containing url - u, err = externalSegmentURL(&ExternalSegment{URL: rawURL}) - host = internal.HostFromURL(u) - if nil != err || host != "url.com" { - t.Error(u, err, internal.HostFromURL(u)) - } - // segment only containing request - u, err = externalSegmentURL(&ExternalSegment{Request: req}) - host = internal.HostFromURL(u) - if nil != err || "request.com" != host { - t.Error(host) - } - // segment only containing response - u, err = externalSegmentURL(&ExternalSegment{Response: response}) - host = internal.HostFromURL(u) - if nil != err || "response.com" != host { - t.Error(host) - } - // segment containing request and response - u, err = externalSegmentURL(&ExternalSegment{ - Request: req, - Response: response, - }) - host = internal.HostFromURL(u) - if nil != err || "response.com" != host { - t.Error(host) - } - // segment containing url, request, and response - u, err = externalSegmentURL(&ExternalSegment{ - URL: rawURL, - Request: req, - Response: response, - }) - host = internal.HostFromURL(u) - if nil != err || "url.com" != host { - t.Error(err, host) - } -} - -func TestZeroSegmentsSafe(t *testing.T) { - s := Segment{} - s.End() - - StartSegmentNow(nil) - - ds := DatastoreSegment{} - ds.End() - - es := ExternalSegment{} - es.End() - - StartSegment(nil, "").End() - - StartExternalSegment(nil, nil).End() -} - -func TestTraceSegmentDefer(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - func() { - defer StartSegment(txn, "segment").End() - }() - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Custom/segment", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/segment", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestTraceSegmentNilErr(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - err := StartSegment(txn, "segment").End() - if nil != err { - t.Error(err) - } - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Custom/segment", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/segment", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestTraceSegmentOutOfOrder(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := StartSegment(txn, "s1") - s2 := StartSegment(txn, "s1") - err1 := s1.End() - err2 := s2.End() - if nil != err1 { - t.Error(err1) - } - if nil == err2 { - t.Error(err2) - } - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Custom/s1", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/s1", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestTraceSegmentEndedBeforeStartSegment(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.End() - s := StartSegment(txn, "segment") - err := s.End() - if err != errAlreadyEnded { - t.Error(err) - } - app.ExpectMetrics(t, webMetrics) -} - -func TestTraceSegmentEndedBeforeEndSegment(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s := StartSegment(txn, "segment") - txn.End() - err := s.End() - if err != errAlreadyEnded { - t.Error(err) - } - - app.ExpectMetrics(t, webMetrics) -} - -func TestTraceSegmentPanic(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - func() { - defer func() { - recover() - }() - - func() { - defer StartSegment(txn, "f1").End() - - func() { - t := StartSegment(txn, "f2") - - func() { - defer StartSegment(txn, "f3").End() - - func() { - StartSegment(txn, "f4") - - panic(nil) - }() - }() - - t.End() - }() - }() - }() - - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Custom/f1", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/f1", Scope: scope, Forced: false, Data: nil}, - {Name: "Custom/f3", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/f3", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) -} - -func TestTraceSegmentNilTxn(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s := Segment{Name: "hello"} - err := s.End() - if err != nil { - t.Error(err) - } - txn.End() - app.ExpectMetrics(t, webMetrics) -} - -func TestTraceDatastore(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s := DatastoreSegment{} - s.StartTime = txn.StartSegmentNow() - s.Product = DatastoreMySQL - s.Collection = "my_table" - s.Operation = "SELECT" - err := s.End() - if nil != err { - t.Error(err) - } - txn.NoticeError(myError{}) - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/MySQL/SELECT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/my_table/SELECT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/my_table/SELECT", Scope: scope, Forced: false, Data: nil}, - }, webErrorMetrics...)) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - "databaseCallCount": 1, - "databaseDuration": internal.MatchAnything, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - "databaseCallCount": 1, - "databaseDuration": internal.MatchAnything, - }, - }}) -} - -func TestTraceDatastoreBackground(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - s := DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: DatastoreMySQL, - Collection: "my_table", - Operation: "SELECT", - } - err := s.End() - if nil != err { - t.Error(err) - } - txn.NoticeError(myError{}) - txn.End() - scope := "OtherTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/MySQL/SELECT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/my_table/SELECT", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/my_table/SELECT", Scope: scope, Forced: false, Data: nil}, - }, backgroundErrorMetrics...)) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - "databaseCallCount": 1, - "databaseDuration": internal.MatchAnything, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "databaseCallCount": 1, - "databaseDuration": internal.MatchAnything, - }, - }}) -} - -func TestTraceDatastoreMissingProductOperationCollection(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s := DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - } - err := s.End() - if nil != err { - t.Error(err) - } - txn.NoticeError(myError{}) - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/Unknown/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/Unknown/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/Unknown/other", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/operation/Unknown/other", Scope: scope, Forced: false, Data: nil}, - }, webErrorMetrics...)) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - "databaseCallCount": 1, - "databaseDuration": internal.MatchAnything, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - "databaseCallCount": 1, - "databaseDuration": internal.MatchAnything, - }, - }}) -} - -func TestTraceDatastoreNilTxn(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - var s DatastoreSegment - s.Product = DatastoreMySQL - s.Collection = "my_table" - s.Operation = "SELECT" - err := s.End() - if nil != err { - t.Error(err) - } - txn.NoticeError(myError{}) - txn.End() - app.ExpectMetrics(t, webErrorMetrics) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - }}) -} - -func TestTraceDatastoreTxnEnded(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.NoticeError(myError{}) - s := DatastoreSegment{ - StartTime: txn.StartSegmentNow(), - Product: DatastoreMySQL, - Collection: "my_table", - Operation: "SELECT", - } - txn.End() - err := s.End() - if errAlreadyEnded != err { - t.Error(err) - } - app.ExpectMetrics(t, webErrorMetrics) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - }}) -} - -func TestTraceExternal(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s := ExternalSegment{ - StartTime: txn.StartSegmentNow(), - URL: "http://example.com/", - } - err := s.End() - if nil != err { - t.Error(err) - } - txn.NoticeError(myError{}) - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/example.com/http", Scope: scope, Forced: false, Data: nil}, - }, webErrorMetrics...)) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - }, - }}) -} - -func TestExternalSegmentCustomFieldsWithURL(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.CrossApplicationTracer.Enabled = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s := ExternalSegment{ - StartTime: txn.StartSegmentNow(), - URL: "https://otherhost.com/path/zip/zap?secret=ssshhh", - Host: "bufnet", - Procedure: "TestApplication/DoUnaryUnary", - Library: "grpc", - } - err := s.End() - if nil != err { - t.Error(err) - } - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/bufnet/grpc/TestApplication/DoUnaryUnary", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "name": "External/bufnet/grpc/TestApplication/DoUnaryUnary", - "category": "http", - "component": "grpc", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - // "http.url" and "http.method" are not saved if - // library is not "http". - }, - }, - }) -} - -func TestExternalSegmentCustomFieldsWithRequest(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.CrossApplicationTracer.Enabled = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - req, _ := http.NewRequest("GET", "https://www.something.com/path/zip/zap?secret=ssshhh", nil) - s := StartExternalSegment(txn, req) - s.Host = "bufnet" - s.Procedure = "TestApplication/DoUnaryUnary" - s.Library = "grpc" - err := s.End() - if nil != err { - t.Error(err) - } - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/bufnet/grpc/TestApplication/DoUnaryUnary", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "name": "External/bufnet/grpc/TestApplication/DoUnaryUnary", - "category": "http", - "component": "grpc", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - // "http.url" and "http.method" are not saved if - // library is not "http". - }, - }, - }) -} - -func TestExternalSegmentCustomFieldsWithResponse(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.CrossApplicationTracer.Enabled = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - req, _ := http.NewRequest("GET", "https://www.something.com/path/zip/zap?secret=ssshhh", nil) - resp := &http.Response{Request: req} - s := ExternalSegment{ - StartTime: txn.StartSegmentNow(), - Response: resp, - Host: "bufnet", - Procedure: "TestApplication/DoUnaryUnary", - Library: "grpc", - } - err := s.End() - if nil != err { - t.Error(err) - } - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/bufnet/grpc/TestApplication/DoUnaryUnary", Scope: scope, Forced: false, Data: nil}, - }, webMetrics...)) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "name": "External/bufnet/grpc/TestApplication/DoUnaryUnary", - "category": "http", - "component": "grpc", - "span.kind": "client", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{ - // "http.url" and "http.method" are not saved if - // library is not "http". - }, - }, - }) -} - -func TestTraceExternalBadURL(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s := ExternalSegment{ - StartTime: txn.StartSegmentNow(), - URL: ":example.com/", - } - err := s.End() - if nil == err { - t.Error(err) - } - txn.NoticeError(myError{}) - txn.End() - app.ExpectMetrics(t, webErrorMetrics) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - }}) -} - -func TestTraceExternalBackground(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - s := ExternalSegment{ - StartTime: txn.StartSegmentNow(), - URL: "http://example.com/", - } - err := s.End() - if nil != err { - t.Error(err) - } - txn.NoticeError(myError{}) - txn.End() - scope := "OtherTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/example.com/http", Scope: scope, Forced: false, Data: nil}, - }, backgroundErrorMetrics...)) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - }, - }}) -} - -func TestTraceExternalMissingURL(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s := ExternalSegment{ - StartTime: txn.StartSegmentNow(), - } - err := s.End() - if nil != err { - t.Error(err) - } - txn.NoticeError(myError{}) - txn.End() - scope := "WebTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, - {Name: "External/unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/unknown/http", Scope: scope, Forced: false, Data: nil}, - }, webErrorMetrics...)) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - }, - }}) -} - -func TestTraceExternalNilTxn(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.NoticeError(myError{}) - var s ExternalSegment - err := s.End() - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectMetrics(t, webErrorMetrics) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - }}) -} - -func TestTraceExternalTxnEnded(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.NoticeError(myError{}) - s := ExternalSegment{ - StartTime: txn.StartSegmentNow(), - URL: "http://example.com/", - } - txn.End() - err := s.End() - if err != errAlreadyEnded { - t.Error(err) - } - app.ExpectMetrics(t, webErrorMetrics) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "WebTransaction/Go/hello", - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "WebTransaction/Go/hello", - "nr.apdexPerfZone": "F", - }, - }}) -} - -func TestRoundTripper(t *testing.T) { - app := testApp(distributedTracingReplyFields, enableBetterCAT, t) - txn := app.StartTransaction("hello", nil, nil) - url := "http://example.com/" - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatal(err) - } - req.Header.Add("zip", "zap") - client := &http.Client{} - inner := roundTripperFunc(func(r *http.Request) (*http.Response, error) { - catHdr := r.Header.Get(DistributedTracePayloadHeader) - if "" == catHdr { - t.Error("cat header missing") - } - // Test that headers are preserved during reqest cloning: - if z := r.Header.Get("zip"); z != "zap" { - t.Error("missing header", z) - } - if r.URL.String() != url { - t.Error(r.URL.String()) - } - return nil, errors.New("hello") - }) - client.Transport = NewRoundTripper(txn, inner) - resp, err := client.Do(req) - if resp != nil || err == nil { - t.Error(resp, err.Error()) - } - // Ensure that the request was cloned: - catHdr := req.Header.Get(DistributedTracePayloadHeader) - if "" != catHdr { - t.Error("cat header unexpectedly present") - } - txn.NoticeError(myError{}) - txn.End() - scope := "OtherTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/example.com/http/GET", Scope: scope, Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Data: nil}, - {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Data: nil}, - {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Data: nil}, - }, backgroundErrorMetrics...)) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - "guid": internal.MatchAnything, - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - "guid": internal.MatchAnything, - "traceId": internal.MatchAnything, - "priority": internal.MatchAnything, - "sampled": internal.MatchAnything, - }, - }}) -} - -func TestRoundTripperOldCAT(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - url := "http://example.com/" - client := &http.Client{} - inner := roundTripperFunc(func(r *http.Request) (*http.Response, error) { - // TODO test that request headers have been set here. - if r.URL.String() != url { - t.Error(r.URL.String()) - } - return nil, errors.New("hello") - }) - client.Transport = NewRoundTripper(txn, inner) - resp, err := client.Get(url) - if resp != nil || err == nil { - t.Error(resp, err.Error()) - } - txn.NoticeError(myError{}) - txn.End() - scope := "OtherTransaction/Go/hello" - app.ExpectMetrics(t, append([]internal.WantMetric{ - {Name: "External/all", Scope: "", Forced: true, Data: nil}, - {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, - {Name: "External/example.com/http/GET", Scope: scope, Forced: false, Data: nil}, - }, backgroundErrorMetrics...)) - app.ExpectErrorEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "error.class": "newrelic.myError", - "error.message": "my msg", - "transactionName": "OtherTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - }, - }}) - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "externalCallCount": 1, - "externalDuration": internal.MatchAnything, - "nr.tripId": internal.MatchAnything, - "nr.guid": internal.MatchAnything, - "nr.pathHash": internal.MatchAnything, - }, - }}) -} - -func TestTraceBelowThreshold(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) -} - -func TestTraceBelowThresholdBackground(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) -} - -func TestTraceNoSegments(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.TransactionTracer.SegmentThreshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - NumSegments: 0, - }}) -} - -func TestTraceDisabledLocally(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.Enabled = false - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) -} - -func TestTraceDisabledByServerSideConfig(t *testing.T) { - // Test that server-side-config trace-enabled-setting can disable transaction - // traces. - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.TransactionTracer.SegmentThreshold = 0 - } - replyfn := func(reply *internal.ConnectReply) { - json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.enabled":false}}`), reply) - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) -} - -func TestTraceEnabledByServerSideConfig(t *testing.T) { - // Test that server-side-config trace-enabled-setting can enable - // transaction traces (and hence server-side-config has priority). - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.Enabled = false - } - replyfn := func(reply *internal.ConnectReply) { - json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.enabled":true}}`), reply) - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - NumSegments: 0, - }}) -} - -func TestTraceDisabledRemotelyOverridesServerSideConfig(t *testing.T) { - // Test that the connect reply "collect_traces" setting overrides the - // "transaction_tracer.enabled" server side config setting. - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.TransactionTracer.SegmentThreshold = 0 - cfg.TransactionTracer.Enabled = true - } - replyfn := func(reply *internal.ConnectReply) { - json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.enabled":true},"collect_traces":false}`), reply) - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) -} - -func TestTraceDisabledRemotely(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.TransactionTracer.SegmentThreshold = 0 - } - replyfn := func(reply *internal.ConnectReply) { - reply.CollectTraces = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) -} - -func TestTraceWithSegments(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.TransactionTracer.SegmentThreshold = 0 - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := StartSegment(txn, "s1") - s1.End() - s2 := ExternalSegment{ - StartTime: StartSegmentNow(txn), - URL: "http://example.com", - } - s2.End() - s3 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "my_table", - Operation: "SELECT", - } - s3.End() - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - NumSegments: 3, - }}) -} - -func TestTraceSegmentsBelowThreshold(t *testing.T) { - cfgfn := func(cfg *Config) { - cfg.TransactionTracer.Threshold.IsApdexFailing = false - cfg.TransactionTracer.Threshold.Duration = 0 - cfg.TransactionTracer.SegmentThreshold = 1 * time.Hour - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, helloRequest) - s1 := StartSegment(txn, "s1") - s1.End() - s2 := ExternalSegment{ - StartTime: StartSegmentNow(txn), - URL: "http://example.com", - } - s2.End() - s3 := DatastoreSegment{ - StartTime: StartSegmentNow(txn), - Product: DatastoreMySQL, - Collection: "my_table", - Operation: "SELECT", - } - s3.End() - txn.End() - app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ - MetricName: "WebTransaction/Go/hello", - NumSegments: 0, - }}) -} - -func TestNoticeErrorTxnEvents(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - err := txn.NoticeError(myError{}) - if nil != err { - t.Error(err) - } - txn.End() - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "error": true, - }, - }}) -} - -func TestTransactionApplication(t *testing.T) { - txn := testApp(nil, nil, t).StartTransaction("hello", nil, nil) - app := txn.Application() - err := app.RecordCustomMetric("myMetric", 123.0) - if nil != err { - t.Error(err) - } - expectData := []float64{1, 123.0, 123.0, 123.0, 123.0, 123.0 * 123.0} - app.(expectApp).ExpectMetrics(t, []internal.WantMetric{ - {Name: "Custom/myMetric", Scope: "", Forced: false, Data: expectData}, - }) -} - -func TestNilSegmentPointerEnd(t *testing.T) { - var basicSegment *Segment - var datastoreSegment *DatastoreSegment - var externalSegment *ExternalSegment - - // These calls on nil pointer receivers should not panic. - basicSegment.End() - datastoreSegment.End() - externalSegment.End() -} - -type flushWriter struct{} - -func (f flushWriter) WriteHeader(int) {} -func (f flushWriter) Write([]byte) (int, error) { return 0, nil } -func (f flushWriter) Header() http.Header { return nil } -func (f flushWriter) Flush() {} - -func TestAsync(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", flushWriter{}, nil) - if _, ok := txn.(http.Flusher); !ok { - t.Error("transaction should have flush") - } - s1 := StartSegment(txn, "mainThread") - asyncThread := txn.NewGoroutine() - // Test that the async transaction reference has the correct optional - // interface behavior. - if _, ok := asyncThread.(http.Flusher); !ok { - t.Error("async transaction reference should have flush") - } - s2 := StartSegment(asyncThread, "asyncThread") - // End segments in interleaved order. - s1.End() - s2.End() - // Test that the async transaction reference has the expected - // transaction method behavior. - asyncThread.AddAttribute("zip", "zap") - // Test that the transaction ends when the async transaction is ended. - if err := asyncThread.End(); nil != err { - t.Error(err) - } - threadAfterEnd := asyncThread.NewGoroutine() - if _, ok := threadAfterEnd.(http.Flusher); !ok { - t.Error("after end transaction reference should have flush") - } - if err := threadAfterEnd.End(); err != errAlreadyEnded { - t.Error(err) - } - app.ExpectTxnEvents(t, []internal.WantEvent{{ - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - }, - UserAttributes: map[string]interface{}{ - "zip": "zap", - }, - }}) - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/mainThread", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/mainThread", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, - {Name: "Custom/asyncThread", Scope: "", Forced: false, Data: nil}, - {Name: "Custom/asyncThread", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, - }) -} - -func TestMessageProducerSegmentBasic(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - s := MessageProducerSegment{ - StartTime: StartSegmentNow(txn), - Library: "RabbitMQ", - DestinationType: MessageQueue, - DestinationName: "myQueue", - } - err := s.End() - if err != nil { - t.Error(err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, - {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, - }) - app.ExpectSpanEvents(t, []internal.WantEvent{ - { - Intrinsics: map[string]interface{}{ - "name": "OtherTransaction/Go/hello", - "sampled": true, - "category": "generic", - "nr.entryPoint": true, - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - { - Intrinsics: map[string]interface{}{ - "parentId": internal.MatchAnything, - "name": "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", - "category": "generic", - }, - UserAttributes: map[string]interface{}{}, - AgentAttributes: map[string]interface{}{}, - }, - }) -} - -func TestMessageProducerSegmentMissingDestinationType(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - s := MessageProducerSegment{ - StartTime: StartSegmentNow(txn), - Library: "RabbitMQ", - DestinationName: "myQueue", - } - err := s.End() - if err != nil { - t.Error(err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, - }) -} - -func TestMessageProducerSegmentTemp(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - s := MessageProducerSegment{ - StartTime: StartSegmentNow(txn), - Library: "RabbitMQ", - DestinationType: MessageQueue, - DestinationTemporary: true, - DestinationName: "myQueue0123456789", - } - err := s.End() - if err != nil { - t.Error(err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/RabbitMQ/Queue/Produce/Temp", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/RabbitMQ/Queue/Produce/Temp", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, - }) -} - -func TestMessageProducerSegmentNoName(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - s := MessageProducerSegment{ - StartTime: StartSegmentNow(txn), - Library: "RabbitMQ", - DestinationType: MessageQueue, - } - err := s.End() - if err != nil { - t.Error(err) - } - txn.End() - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/Unknown", Scope: "", Forced: false, Data: nil}, - {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/Unknown", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, - }) -} - -func TestMessageProducerSegmentTxnEnded(t *testing.T) { - app := testApp(nil, nil, t) - txn := app.StartTransaction("hello", nil, nil) - s := MessageProducerSegment{ - StartTime: StartSegmentNow(txn), - Library: "RabbitMQ", - DestinationType: MessageQueue, - DestinationTemporary: true, - DestinationName: "myQueue0123456789", - } - txn.End() - err := s.End() - if err != errAlreadyEnded { - t.Error("expected already ended error", err) - } - app.ExpectMetrics(t, []internal.WantMetric{ - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - }) -} - -func TestMessageProducerSegmentNilTxn(t *testing.T) { - s := MessageProducerSegment{ - StartTime: StartSegmentNow(nil), - Library: "RabbitMQ", - DestinationType: MessageQueue, - DestinationTemporary: true, - DestinationName: "myQueue0123456789", - } - s.End() -} - -func TestMessageProducerSegmentNilSegment(t *testing.T) { - var s *MessageProducerSegment - s.End() -} diff --git a/internal_txn.go b/internal_txn.go deleted file mode 100644 index 206c77fca..000000000 --- a/internal_txn.go +++ /dev/null @@ -1,1246 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "errors" - "fmt" - "net/http" - "net/url" - "reflect" - "strings" - "sync" - "time" - - "github.com/newrelic/go-agent/internal" -) - -type txnInput struct { - // This ResponseWriter should only be accessed using txn.getWriter() - writer http.ResponseWriter - app Application - Consumer dataConsumer - *appRun -} - -type txn struct { - txnInput - // This mutex is required since the consumer may call the public API - // interface functions from different routines. - sync.Mutex - // finished indicates whether or not End() has been called. After - // finished has been set to true, no recording should occur. - finished bool - numPayloadsCreated uint32 - sampledCalculated bool - - ignore bool - - // wroteHeader prevents capturing multiple response code errors if the - // user erroneously calls WriteHeader multiple times. - wroteHeader bool - - internal.TxnData - - mainThread internal.Thread - asyncThreads []*internal.Thread -} - -type thread struct { - *txn - // thread does not have locking because it should only be accessed while - // the txn is locked. - thread *internal.Thread -} - -func (txn *txn) markStart(now time.Time) { - txn.Start = now - // The mainThread is considered active now. - txn.mainThread.RecordActivity(now) - -} - -func (txn *txn) markEnd(now time.Time, thread *internal.Thread) { - txn.Stop = now - // The thread on which End() was called is considered active now. - thread.RecordActivity(now) - txn.Duration = txn.Stop.Sub(txn.Start) - - // TotalTime is the sum of "active time" across all threads. A thread - // was active when it started the transaction, stopped the transaction, - // started a segment, or stopped a segment. - txn.TotalTime = txn.mainThread.TotalTime() - for _, thd := range txn.asyncThreads { - txn.TotalTime += thd.TotalTime() - } - // Ensure that TotalTime is at least as large as Duration so that the - // graphs look sensible. This can happen under the following situation: - // goroutine1: txn.start----|segment1| - // goroutine2: |segment2|----txn.end - if txn.Duration > txn.TotalTime { - txn.TotalTime = txn.Duration - } -} - -func newTxn(input txnInput, name string) *thread { - txn := &txn{ - txnInput: input, - } - txn.markStart(time.Now()) - - txn.Name = name - txn.Attrs = internal.NewAttributes(input.AttributeConfig) - - if input.Config.DistributedTracer.Enabled { - txn.BetterCAT.Enabled = true - txn.BetterCAT.Priority = internal.NewPriority() - txn.TraceIDGenerator = input.Reply.TraceIDGenerator - txn.BetterCAT.ID = txn.TraceIDGenerator.GenerateTraceID() - txn.SpanEventsEnabled = txn.Config.SpanEvents.Enabled - txn.LazilyCalculateSampled = txn.lazilyCalculateSampled - } - - txn.Attrs.Agent.Add(internal.AttributeHostDisplayName, txn.Config.HostDisplayName, nil) - txn.TxnTrace.Enabled = txn.Config.TransactionTracer.Enabled - txn.TxnTrace.SegmentThreshold = txn.Config.TransactionTracer.SegmentThreshold - txn.StackTraceThreshold = txn.Config.TransactionTracer.StackTraceThreshold - txn.SlowQueriesEnabled = txn.Config.DatastoreTracer.SlowQuery.Enabled - txn.SlowQueryThreshold = txn.Config.DatastoreTracer.SlowQuery.Threshold - - // Synthetics support is tied up with a transaction's Old CAT field, - // CrossProcess. To support Synthetics with either BetterCAT or Old CAT, - // Initialize the CrossProcess field of the transaction, passing in - // the top-level configuration. - doOldCAT := txn.Config.CrossApplicationTracer.Enabled - noGUID := txn.Config.DistributedTracer.Enabled - txn.CrossProcess.Init(doOldCAT, noGUID, input.Reply) - - return &thread{ - txn: txn, - thread: &txn.mainThread, - } -} - -// lazilyCalculateSampled calculates and returns whether or not the transaction -// should be sampled. Sampled is not computed at the beginning of the -// transaction because we want to calculate Sampled only for transactions that -// do not accept an inbound payload. -func (txn *txn) lazilyCalculateSampled() bool { - if !txn.BetterCAT.Enabled { - return false - } - if txn.sampledCalculated { - return txn.BetterCAT.Sampled - } - txn.BetterCAT.Sampled = txn.Reply.AdaptiveSampler.ComputeSampled(txn.BetterCAT.Priority.Float32(), time.Now()) - if txn.BetterCAT.Sampled { - txn.BetterCAT.Priority += 1.0 - } - txn.sampledCalculated = true - return txn.BetterCAT.Sampled -} - -type requestWrap struct{ request *http.Request } - -func (r requestWrap) Header() http.Header { return r.request.Header } -func (r requestWrap) URL() *url.URL { return r.request.URL } -func (r requestWrap) Method() string { return r.request.Method } - -func (r requestWrap) Transport() TransportType { - if strings.HasPrefix(r.request.Proto, "HTTP") { - if r.request.TLS != nil { - return TransportHTTPS - } - return TransportHTTP - } - return TransportUnknown - -} - -type staticWebRequest struct { - header http.Header - url *url.URL - method string - transport TransportType -} - -func (r staticWebRequest) Header() http.Header { return r.header } -func (r staticWebRequest) URL() *url.URL { return r.url } -func (r staticWebRequest) Method() string { return r.method } -func (r staticWebRequest) Transport() TransportType { return TransportHTTP } - -func (txn *txn) SetWebRequest(r WebRequest) error { - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return errAlreadyEnded - } - - // Any call to SetWebRequest should indicate a web transaction. - txn.IsWeb = true - - if nil == r { - return nil - } - h := r.Header() - if nil != h { - txn.Queuing = internal.QueueDuration(h, txn.Start) - - if p := h.Get(DistributedTracePayloadHeader); p != "" { - txn.acceptDistributedTracePayloadLocked(r.Transport(), p) - } - - txn.CrossProcess.InboundHTTPRequest(h) - } - - internal.RequestAgentAttributes(txn.Attrs, r.Method(), h, r.URL()) - - return nil -} - -func (thd *thread) SetWebResponse(w http.ResponseWriter) Transaction { - txn := thd.txn - txn.Lock() - defer txn.Unlock() - - // Replace the ResponseWriter even if the transaction has ended so that - // consumers calling ResponseWriter methods on the transactions see that - // data flowing through as expected. - txn.writer = w - - return upgradeTxn(&thread{ - thread: thd.thread, - txn: txn, - }) -} - -func (txn *txn) freezeName() { - if txn.ignore || ("" != txn.FinalName) { - return - } - - txn.FinalName = internal.CreateFullTxnName(txn.Name, txn.Reply, txn.IsWeb) - if "" == txn.FinalName { - txn.ignore = true - } -} - -func (txn *txn) getsApdex() bool { - return txn.IsWeb -} - -func (txn *txn) shouldSaveTrace() bool { - if !txn.Config.TransactionTracer.Enabled { - return false - } - if txn.CrossProcess.IsSynthetics() { - return true - } - return txn.Duration >= txn.txnTraceThreshold(txn.ApdexThreshold) -} - -func (txn *txn) MergeIntoHarvest(h *internal.Harvest) { - - var priority internal.Priority - if txn.BetterCAT.Enabled { - priority = txn.BetterCAT.Priority - } else { - priority = internal.NewPriority() - } - - internal.CreateTxnMetrics(&txn.TxnData, h.Metrics) - internal.MergeBreakdownMetrics(&txn.TxnData, h.Metrics) - - if txn.Config.TransactionEvents.Enabled { - // Allocate a new TxnEvent to prevent a reference to the large transaction. - alloc := new(internal.TxnEvent) - *alloc = txn.TxnData.TxnEvent - h.TxnEvents.AddTxnEvent(alloc, priority) - } - - if txn.Reply.CollectErrors { - internal.MergeTxnErrors(&h.ErrorTraces, txn.Errors, txn.TxnEvent) - } - - if txn.Config.ErrorCollector.CaptureEvents { - for _, e := range txn.Errors { - errEvent := &internal.ErrorEvent{ - ErrorData: *e, - TxnEvent: txn.TxnEvent, - } - // Since the stack trace is not used in error events, remove the reference - // to minimize memory. - errEvent.Stack = nil - h.ErrorEvents.Add(errEvent, priority) - } - } - - if txn.shouldSaveTrace() { - h.TxnTraces.Witness(internal.HarvestTrace{ - TxnEvent: txn.TxnEvent, - Trace: txn.TxnTrace, - }) - } - - if nil != txn.SlowQueries { - h.SlowSQLs.Merge(txn.SlowQueries, txn.TxnEvent) - } - - if txn.BetterCAT.Sampled && txn.SpanEventsEnabled { - h.SpanEvents.MergeFromTransaction(&txn.TxnData) - } -} - -func headersJustWritten(txn *txn, code int, hdr http.Header) { - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return - } - if txn.wroteHeader { - return - } - txn.wroteHeader = true - - internal.ResponseHeaderAttributes(txn.Attrs, hdr) - internal.ResponseCodeAttribute(txn.Attrs, code) - - if txn.appRun.responseCodeIsError(code) { - e := internal.TxnErrorFromResponseCode(time.Now(), code) - e.Stack = internal.GetStackTrace() - txn.noticeErrorInternal(e) - } -} - -func (txn *txn) responseHeader(hdr http.Header) http.Header { - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return nil - } - if txn.wroteHeader { - return nil - } - if !txn.CrossProcess.Enabled { - return nil - } - if !txn.CrossProcess.IsInbound() { - return nil - } - txn.freezeName() - contentLength := internal.GetContentLengthFromHeader(hdr) - - appData, err := txn.CrossProcess.CreateAppData(txn.FinalName, txn.Queuing, time.Since(txn.Start), contentLength) - if err != nil { - txn.Config.Logger.Debug("error generating outbound response header", map[string]interface{}{ - "error": err, - }) - return nil - } - return internal.AppDataToHTTPHeader(appData) -} - -func addCrossProcessHeaders(txn *txn, hdr http.Header) { - // responseHeader() checks the wroteHeader field and returns a nil map if the - // header has been written, so we don't need a check here. - if nil != hdr { - for key, values := range txn.responseHeader(hdr) { - for _, value := range values { - hdr.Add(key, value) - } - } - } -} - -// getWriter is used to access the transaction's ResponseWriter. The -// ResponseWriter is mutex protected since it may be changed with -// txn.SetWebResponse, and we want changes to be visible across goroutines. The -// ResponseWriter is accessed using this getWriter() function rather than directly -// in mutex protected methods since we do NOT want the transaction to be locked -// while calling the ResponseWriter's methods. -func (txn *txn) getWriter() http.ResponseWriter { - txn.Lock() - rw := txn.writer - txn.Unlock() - return rw -} - -func nilSafeHeader(rw http.ResponseWriter) http.Header { - if nil == rw { - return nil - } - return rw.Header() -} - -func (txn *txn) Header() http.Header { - return nilSafeHeader(txn.getWriter()) -} - -func (txn *txn) Write(b []byte) (n int, err error) { - rw := txn.getWriter() - hdr := nilSafeHeader(rw) - - // This is safe to call unconditionally, even if Write() is called multiple - // times; see also the commentary in addCrossProcessHeaders(). - addCrossProcessHeaders(txn, hdr) - - if rw != nil { - n, err = rw.Write(b) - } - - headersJustWritten(txn, http.StatusOK, hdr) - - return -} - -func (txn *txn) WriteHeader(code int) { - rw := txn.getWriter() - hdr := nilSafeHeader(rw) - - addCrossProcessHeaders(txn, hdr) - - if nil != rw { - rw.WriteHeader(code) - } - - headersJustWritten(txn, code, hdr) -} - -func (thd *thread) End() error { - txn := thd.txn - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return errAlreadyEnded - } - - txn.finished = true - - r := recover() - if nil != r { - e := internal.TxnErrorFromPanic(time.Now(), r) - e.Stack = internal.GetStackTrace() - txn.noticeErrorInternal(e) - } - - txn.markEnd(time.Now(), thd.thread) - txn.freezeName() - // Make a sampling decision if there have been no segments or outbound - // payloads. - txn.lazilyCalculateSampled() - - // Finalise the CAT state. - if err := txn.CrossProcess.Finalise(txn.Name, txn.Config.AppName); err != nil { - txn.Config.Logger.Debug("error finalising the cross process state", map[string]interface{}{ - "error": err, - }) - } - - // Assign apdexThreshold regardless of whether or not the transaction - // gets apdex since it may be used to calculate the trace threshold. - txn.ApdexThreshold = internal.CalculateApdexThreshold(txn.Reply, txn.FinalName) - - if txn.getsApdex() { - if txn.HasErrors() { - txn.Zone = internal.ApdexFailing - } else { - txn.Zone = internal.CalculateApdexZone(txn.ApdexThreshold, txn.Duration) - } - } else { - txn.Zone = internal.ApdexNone - } - - if txn.Config.Logger.DebugEnabled() { - txn.Config.Logger.Debug("transaction ended", map[string]interface{}{ - "name": txn.FinalName, - "duration_ms": txn.Duration.Seconds() * 1000.0, - "ignored": txn.ignore, - "app_connected": "" != txn.Reply.RunID, - }) - } - - if !txn.ignore { - txn.Consumer.Consume(txn.Reply.RunID, txn) - } - - // Note that if a consumer uses `panic(nil)`, the panic will not - // propagate. - if nil != r { - panic(r) - } - - return nil -} - -func (txn *txn) AddAttribute(name string, value interface{}) error { - txn.Lock() - defer txn.Unlock() - - if txn.Config.HighSecurity { - return errHighSecurityEnabled - } - - if !txn.Reply.SecurityPolicies.CustomParameters.Enabled() { - return errSecurityPolicy - } - - if txn.finished { - return errAlreadyEnded - } - - return internal.AddUserAttribute(txn.Attrs, name, value, internal.DestAll) -} - -var ( - errorsDisabled = errors.New("errors disabled") - errNilError = errors.New("nil error") - errAlreadyEnded = errors.New("transaction has already ended") - errSecurityPolicy = errors.New("disabled by security policy") - errTransactionIgnored = errors.New("transaction has been ignored") - errBrowserDisabled = errors.New("browser disabled by local configuration") -) - -const ( - highSecurityErrorMsg = "message removed by high security setting" - securityPolicyErrorMsg = "message removed by security policy" -) - -func (txn *txn) noticeErrorInternal(err internal.ErrorData) error { - if !txn.Config.ErrorCollector.Enabled { - return errorsDisabled - } - - if nil == txn.Errors { - txn.Errors = internal.NewTxnErrors(internal.MaxTxnErrors) - } - - if txn.Config.HighSecurity { - err.Msg = highSecurityErrorMsg - } - - if !txn.Reply.SecurityPolicies.AllowRawExceptionMessages.Enabled() { - err.Msg = securityPolicyErrorMsg - } - - txn.Errors.Add(err) - txn.TxnData.TxnEvent.HasError = true //mark transaction as having an error - return nil -} - -var ( - errTooManyErrorAttributes = fmt.Errorf("too many extra attributes: limit is %d", - internal.AttributeErrorLimit) -) - -// errorCause returns the error's deepest wrapped ancestor. -func errorCause(err error) error { - for { - if unwrapper, ok := err.(interface{ Unwrap() error }); ok { - if next := unwrapper.Unwrap(); nil != next { - err = next - continue - } - } - return err - } -} - -func errorClassMethod(err error) string { - if ec, ok := err.(ErrorClasser); ok { - return ec.ErrorClass() - } - return "" -} - -func errorStackTraceMethod(err error) internal.StackTrace { - if st, ok := err.(StackTracer); ok { - return st.StackTrace() - } - return nil -} - -func errorAttributesMethod(err error) map[string]interface{} { - if st, ok := err.(ErrorAttributer); ok { - return st.ErrorAttributes() - } - return nil -} - -func errDataFromError(input error) (data internal.ErrorData, err error) { - cause := errorCause(input) - - data = internal.ErrorData{ - When: time.Now(), - Msg: input.Error(), - } - - if c := errorClassMethod(input); "" != c { - // If the error implements ErrorClasser, use that. - data.Klass = c - } else if c := errorClassMethod(cause); "" != c { - // Otherwise, if the error's cause implements ErrorClasser, use that. - data.Klass = c - } else { - // As a final fallback, use the type of the error's cause. - data.Klass = reflect.TypeOf(cause).String() - } - - if st := errorStackTraceMethod(input); nil != st { - // If the error implements StackTracer, use that. - data.Stack = st - } else if st := errorStackTraceMethod(cause); nil != st { - // Otherwise, if the error's cause implements StackTracer, use that. - data.Stack = st - } else { - // As a final fallback, generate a StackTrace here. - data.Stack = internal.GetStackTrace() - } - - var unvetted map[string]interface{} - if ats := errorAttributesMethod(input); nil != ats { - // If the error implements ErrorAttributer, use that. - unvetted = ats - } else { - // Otherwise, if the error's cause implements ErrorAttributer, use that. - unvetted = errorAttributesMethod(cause) - } - if unvetted != nil { - if len(unvetted) > internal.AttributeErrorLimit { - err = errTooManyErrorAttributes - return - } - - data.ExtraAttributes = make(map[string]interface{}) - for key, val := range unvetted { - val, err = internal.ValidateUserAttribute(key, val) - if nil != err { - return - } - data.ExtraAttributes[key] = val - } - } - - return data, nil -} - -func (txn *txn) NoticeError(input error) error { - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return errAlreadyEnded - } - - if nil == input { - return errNilError - } - - data, err := errDataFromError(input) - if nil != err { - return err - } - - if txn.Config.HighSecurity || !txn.Reply.SecurityPolicies.CustomParameters.Enabled() { - data.ExtraAttributes = nil - } - - return txn.noticeErrorInternal(data) -} - -func (txn *txn) SetName(name string) error { - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return errAlreadyEnded - } - - txn.Name = name - return nil -} - -func (txn *txn) Ignore() error { - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return errAlreadyEnded - } - txn.ignore = true - return nil -} - -func (thd *thread) StartSegmentNow() SegmentStartTime { - var s internal.SegmentStartTime - txn := thd.txn - txn.Lock() - if !txn.finished { - s = internal.StartSegment(&txn.TxnData, thd.thread, time.Now()) - } - txn.Unlock() - return SegmentStartTime{ - segment: segment{ - start: s, - thread: thd, - }, - } -} - -const ( - // Browser fields are encoded using the first digits of the license - // key. - browserEncodingKeyLimit = 13 -) - -func browserEncodingKey(licenseKey string) []byte { - key := []byte(licenseKey) - if len(key) > browserEncodingKeyLimit { - key = key[0:browserEncodingKeyLimit] - } - return key -} - -func (txn *txn) BrowserTimingHeader() (*BrowserTimingHeader, error) { - txn.Lock() - defer txn.Unlock() - - if !txn.Config.BrowserMonitoring.Enabled { - return nil, errBrowserDisabled - } - - if txn.Reply.AgentLoader == "" { - // If the loader is empty, either browser has been disabled - // by the server or the application is not yet connected. - return nil, nil - } - - if txn.finished { - return nil, errAlreadyEnded - } - - txn.freezeName() - - // Freezing the name might cause the transaction to be ignored, so check - // this after txn.freezeName(). - if txn.ignore { - return nil, errTransactionIgnored - } - - encodingKey := browserEncodingKey(txn.Config.License) - - attrs, err := internal.Obfuscate(internal.BrowserAttributes(txn.Attrs), encodingKey) - if err != nil { - return nil, fmt.Errorf("error getting browser attributes: %v", err) - } - - name, err := internal.Obfuscate([]byte(txn.FinalName), encodingKey) - if err != nil { - return nil, fmt.Errorf("error obfuscating name: %v", err) - } - - return &BrowserTimingHeader{ - agentLoader: txn.Reply.AgentLoader, - info: browserInfo{ - Beacon: txn.Reply.Beacon, - LicenseKey: txn.Reply.BrowserKey, - ApplicationID: txn.Reply.AppID, - TransactionName: name, - QueueTimeMillis: txn.Queuing.Nanoseconds() / (1000 * 1000), - ApplicationTimeMillis: time.Now().Sub(txn.Start).Nanoseconds() / (1000 * 1000), - ObfuscatedAttributes: attrs, - ErrorBeacon: txn.Reply.ErrorBeacon, - Agent: txn.Reply.JSAgentFile, - }, - }, nil -} - -func createThread(txn *txn) *internal.Thread { - newThread := internal.NewThread(&txn.TxnData) - txn.asyncThreads = append(txn.asyncThreads, newThread) - return newThread -} - -func (thd *thread) NewGoroutine() Transaction { - txn := thd.txn - txn.Lock() - defer txn.Unlock() - - if txn.finished { - // If the transaction has finished, return the same thread. - return upgradeTxn(thd) - } - return upgradeTxn(&thread{ - thread: createThread(txn), - txn: txn, - }) -} - -type segment struct { - start internal.SegmentStartTime - thread *thread -} - -func endSegment(s *Segment) error { - if nil == s { - return nil - } - thd := s.StartTime.thread - if nil == thd { - return nil - } - txn := thd.txn - var err error - txn.Lock() - if txn.finished { - err = errAlreadyEnded - } else { - err = internal.EndBasicSegment(&txn.TxnData, thd.thread, s.StartTime.start, time.Now(), s.Name) - } - txn.Unlock() - return err -} - -func endDatastore(s *DatastoreSegment) error { - if nil == s { - return nil - } - thd := s.StartTime.thread - if nil == thd { - return nil - } - txn := thd.txn - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return errAlreadyEnded - } - if txn.Config.HighSecurity { - s.QueryParameters = nil - } - if !txn.Config.DatastoreTracer.QueryParameters.Enabled { - s.QueryParameters = nil - } - if txn.Reply.SecurityPolicies.RecordSQL.IsSet() { - s.QueryParameters = nil - if !txn.Reply.SecurityPolicies.RecordSQL.Enabled() { - s.ParameterizedQuery = "" - } - } - if !txn.Config.DatastoreTracer.DatabaseNameReporting.Enabled { - s.DatabaseName = "" - } - if !txn.Config.DatastoreTracer.InstanceReporting.Enabled { - s.Host = "" - s.PortPathOrID = "" - } - return internal.EndDatastoreSegment(internal.EndDatastoreParams{ - TxnData: &txn.TxnData, - Thread: thd.thread, - Start: s.StartTime.start, - Now: time.Now(), - Product: string(s.Product), - Collection: s.Collection, - Operation: s.Operation, - ParameterizedQuery: s.ParameterizedQuery, - QueryParameters: s.QueryParameters, - Host: s.Host, - PortPathOrID: s.PortPathOrID, - Database: s.DatabaseName, - }) -} - -func externalSegmentMethod(s *ExternalSegment) string { - if "" != s.Procedure { - return s.Procedure - } - r := s.Request - if nil != s.Response && nil != s.Response.Request { - r = s.Response.Request - } - - if nil != r { - if "" != r.Method { - return r.Method - } - // Golang's http package states that when a client's Request has - // an empty string for Method, the method is GET. - return "GET" - } - - return "" -} - -func externalSegmentURL(s *ExternalSegment) (*url.URL, error) { - if "" != s.URL { - return url.Parse(s.URL) - } - r := s.Request - if nil != s.Response && nil != s.Response.Request { - r = s.Response.Request - } - if r != nil { - return r.URL, nil - } - return nil, nil -} - -func endExternal(s *ExternalSegment) error { - if nil == s { - return nil - } - thd := s.StartTime.thread - if nil == thd { - return nil - } - txn := thd.txn - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return errAlreadyEnded - } - u, err := externalSegmentURL(s) - if nil != err { - return err - } - return internal.EndExternalSegment(internal.EndExternalParams{ - TxnData: &txn.TxnData, - Thread: thd.thread, - Start: s.StartTime.start, - Now: time.Now(), - Logger: txn.Config.Logger, - Response: s.Response, - URL: u, - Host: s.Host, - Library: s.Library, - Method: externalSegmentMethod(s), - }) -} - -func endMessage(s *MessageProducerSegment) error { - if nil == s { - return nil - } - thd := s.StartTime.thread - if nil == thd { - return nil - } - txn := thd.txn - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return errAlreadyEnded - } - - if "" == s.DestinationType { - s.DestinationType = MessageQueue - } - - return internal.EndMessageSegment(internal.EndMessageParams{ - TxnData: &txn.TxnData, - Thread: thd.thread, - Start: s.StartTime.start, - Now: time.Now(), - Library: s.Library, - Logger: txn.Config.Logger, - DestinationName: s.DestinationName, - DestinationType: string(s.DestinationType), - DestinationTemp: s.DestinationTemporary, - }) -} - -// oldCATOutboundHeaders generates the Old CAT and Synthetics headers, depending -// on whether Old CAT is enabled or any Synthetics functionality has been -// triggered in the agent. -func oldCATOutboundHeaders(txn *txn) http.Header { - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return http.Header{} - } - - metadata, err := txn.CrossProcess.CreateCrossProcessMetadata(txn.Name, txn.Config.AppName) - if err != nil { - txn.Config.Logger.Debug("error generating outbound headers", map[string]interface{}{ - "error": err, - }) - - // It's possible for CreateCrossProcessMetadata() to error and still have a - // Synthetics header, so we'll still fall through to returning headers - // based on whatever metadata was returned. - } - - return internal.MetadataToHTTPHeader(metadata) -} - -func outboundHeaders(s *ExternalSegment) http.Header { - thd := s.StartTime.thread - - if nil == thd { - return http.Header{} - } - txn := thd.txn - hdr := oldCATOutboundHeaders(txn) - - // hdr may be empty, or it may contain headers. If DistributedTracer - // is enabled, add more to the existing hdr - if p := thd.CreateDistributedTracePayload().HTTPSafe(); "" != p { - hdr.Add(DistributedTracePayloadHeader, p) - return hdr - } - - return hdr -} - -const ( - maxSampledDistributedPayloads = 35 -) - -type shimPayload struct{} - -func (s shimPayload) Text() string { return "" } -func (s shimPayload) HTTPSafe() string { return "" } - -func (thd *thread) CreateDistributedTracePayload() (payload DistributedTracePayload) { - payload = shimPayload{} - - txn := thd.txn - txn.Lock() - defer txn.Unlock() - - if !txn.BetterCAT.Enabled { - return - } - - if txn.finished { - txn.CreatePayloadException = true - return - } - - if "" == txn.Reply.AccountID || "" == txn.Reply.TrustedAccountKey { - // We can't create a payload: The application is not yet - // connected or serverless distributed tracing configuration was - // not provided. - return - } - - txn.numPayloadsCreated++ - - var p internal.Payload - p.Type = internal.CallerType - p.Account = txn.Reply.AccountID - - p.App = txn.Reply.PrimaryAppID - p.TracedID = txn.BetterCAT.TraceID() - p.Priority = txn.BetterCAT.Priority - p.Timestamp.Set(time.Now()) - p.TransactionID = txn.BetterCAT.ID // Set the transaction ID to the transaction guid. - - if txn.Reply.AccountID != txn.Reply.TrustedAccountKey { - p.TrustedAccountKey = txn.Reply.TrustedAccountKey - } - - sampled := txn.lazilyCalculateSampled() - if sampled && txn.SpanEventsEnabled { - p.ID = txn.CurrentSpanIdentifier(thd.thread) - } - - // limit the number of outbound sampled=true payloads to prevent too - // many downstream sampled events. - p.SetSampled(false) - if txn.numPayloadsCreated < maxSampledDistributedPayloads { - p.SetSampled(sampled) - } - - txn.CreatePayloadSuccess = true - - payload = p - return -} - -var ( - errOutboundPayloadCreated = errors.New("outbound payload already created") - errAlreadyAccepted = errors.New("AcceptDistributedTracePayload has already been called") - errInboundPayloadDTDisabled = errors.New("DistributedTracer must be enabled to accept an inbound payload") - errTrustedAccountKey = errors.New("trusted account key missing or does not match") -) - -func (txn *txn) AcceptDistributedTracePayload(t TransportType, p interface{}) error { - txn.Lock() - defer txn.Unlock() - - return txn.acceptDistributedTracePayloadLocked(t, p) -} - -func (txn *txn) acceptDistributedTracePayloadLocked(t TransportType, p interface{}) error { - - if !txn.BetterCAT.Enabled { - return errInboundPayloadDTDisabled - } - - if txn.finished { - txn.AcceptPayloadException = true - return errAlreadyEnded - } - - if txn.numPayloadsCreated > 0 { - txn.AcceptPayloadCreateBeforeAccept = true - return errOutboundPayloadCreated - } - - if txn.BetterCAT.Inbound != nil { - txn.AcceptPayloadIgnoredMultiple = true - return errAlreadyAccepted - } - - if nil == p { - txn.AcceptPayloadNullPayload = true - return nil - } - - if "" == txn.Reply.AccountID || "" == txn.Reply.TrustedAccountKey { - // We can't accept a payload: The application is not yet - // connected or serverless distributed tracing configuration was - // not provided. - return nil - } - - payload, err := internal.AcceptPayload(p) - if nil != err { - if _, ok := err.(internal.ErrPayloadParse); ok { - txn.AcceptPayloadParseException = true - } else if _, ok := err.(internal.ErrUnsupportedPayloadVersion); ok { - txn.AcceptPayloadIgnoredVersion = true - } else if _, ok := err.(internal.ErrPayloadMissingField); ok { - txn.AcceptPayloadParseException = true - } else { - txn.AcceptPayloadException = true - } - return err - } - - if nil == payload { - return nil - } - - // now that we have a parsed and alloc'd payload, - // let's make sure it has the correct fields - if err := payload.IsValid(); nil != err { - txn.AcceptPayloadParseException = true - return err - } - - // and let's also do our trustedKey check - receivedTrustKey := payload.TrustedAccountKey - if "" == receivedTrustKey { - receivedTrustKey = payload.Account - } - if receivedTrustKey != txn.Reply.TrustedAccountKey { - txn.AcceptPayloadUntrustedAccount = true - return errTrustedAccountKey - } - - if 0 != payload.Priority { - txn.BetterCAT.Priority = payload.Priority - } - - // a nul payload.Sampled means the a field wasn't provided - if nil != payload.Sampled { - txn.BetterCAT.Sampled = *payload.Sampled - txn.sampledCalculated = true - } - - txn.BetterCAT.Inbound = payload - - // TransportType's name field is not mutable outside of its package - // so the only check needed is if the caller is using an empty TransportType - txn.BetterCAT.Inbound.TransportType = t.name - if t.name == "" { - txn.BetterCAT.Inbound.TransportType = TransportUnknown.name - txn.Config.Logger.Debug("Invalid transport type, defaulting to Unknown", map[string]interface{}{}) - } - - if tm := payload.Timestamp.Time(); txn.Start.After(tm) { - txn.BetterCAT.Inbound.TransportDuration = txn.Start.Sub(tm) - } - - txn.AcceptPayloadSuccess = true - - return nil -} - -func (txn *txn) Application() Application { - return txn.app -} - -func (thd *thread) AddAgentSpanAttribute(key internal.SpanAttribute, val string) { - thd.thread.AddAgentSpanAttribute(key, val) -} - -var ( - // Ensure that txn implements AddAgentAttributer to avoid breaking - // integration package type assertions. - _ internal.AddAgentAttributer = &txn{} -) - -func (txn *txn) AddAgentAttribute(id internal.AgentAttributeID, stringVal string, otherVal interface{}) { - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return - } - txn.Attrs.Agent.Add(id, stringVal, otherVal) -} - -func (thd *thread) GetTraceMetadata() (metadata TraceMetadata) { - txn := thd.txn - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return - } - - if txn.BetterCAT.Enabled { - metadata.TraceID = txn.BetterCAT.TraceID() - if txn.SpanEventsEnabled && txn.lazilyCalculateSampled() { - metadata.SpanID = txn.CurrentSpanIdentifier(thd.thread) - } - } - - return -} - -func (thd *thread) GetLinkingMetadata() (metadata LinkingMetadata) { - txn := thd.txn - metadata.EntityName = txn.appRun.firstAppName - metadata.EntityType = "SERVICE" - metadata.EntityGUID = txn.appRun.Reply.EntityGUID - metadata.Hostname = internal.ThisHost - - md := thd.GetTraceMetadata() - metadata.TraceID = md.TraceID - metadata.SpanID = md.SpanID - - return -} - -func (txn *txn) IsSampled() bool { - txn.Lock() - defer txn.Unlock() - - if txn.finished { - return false - } - - return txn.lazilyCalculateSampled() -} diff --git a/internal_txn_test.go b/internal_txn_test.go deleted file mode 100644 index 227c9dd46..000000000 --- a/internal_txn_test.go +++ /dev/null @@ -1,556 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "testing" - "time" - - "github.com/newrelic/go-agent/internal" - "github.com/newrelic/go-agent/internal/cat" - "github.com/newrelic/go-agent/internal/sysinfo" -) - -func TestShouldSaveTrace(t *testing.T) { - for _, tc := range []struct { - name string - expected bool - synthetics bool - tracerEnabled bool - collectTraces bool - duration time.Duration - threshold time.Duration - }{ - { - name: "insufficient duration, all disabled", - expected: false, - synthetics: false, - tracerEnabled: false, - collectTraces: false, - duration: 1 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "insufficient duration, only synthetics enabled", - expected: false, - synthetics: true, - tracerEnabled: false, - collectTraces: false, - duration: 1 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "insufficient duration, only tracer enabled", - expected: false, - synthetics: false, - tracerEnabled: true, - collectTraces: false, - duration: 1 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "insufficient duration, only collect traces enabled", - expected: false, - synthetics: false, - tracerEnabled: false, - collectTraces: true, - duration: 1 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "insufficient duration, all normal flags enabled", - expected: false, - synthetics: false, - tracerEnabled: true, - collectTraces: true, - duration: 1 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "insufficient duration, all flags enabled", - expected: true, - synthetics: true, - tracerEnabled: true, - collectTraces: true, - duration: 1 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "sufficient duration, all disabled", - expected: false, - synthetics: false, - tracerEnabled: false, - collectTraces: false, - duration: 3 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "sufficient duration, only synthetics enabled", - expected: false, - synthetics: true, - tracerEnabled: false, - collectTraces: false, - duration: 3 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "sufficient duration, only tracer enabled", - expected: false, - synthetics: false, - tracerEnabled: true, - collectTraces: false, - duration: 3 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "sufficient duration, only collect traces enabled", - expected: false, - synthetics: false, - tracerEnabled: false, - collectTraces: true, - duration: 3 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "sufficient duration, all normal flags enabled", - expected: true, - synthetics: false, - tracerEnabled: true, - collectTraces: true, - duration: 3 * time.Second, - threshold: 2 * time.Second, - }, - { - name: "sufficient duration, all flags enabled", - expected: true, - synthetics: true, - tracerEnabled: true, - collectTraces: true, - duration: 3 * time.Second, - threshold: 2 * time.Second, - }, - } { - txn := &txn{} - - cfg := NewConfig("my app", "0123456789012345678901234567890123456789") - cfg.TransactionTracer.Enabled = tc.tracerEnabled - cfg.TransactionTracer.Threshold.Duration = tc.threshold - cfg.TransactionTracer.Threshold.IsApdexFailing = false - reply := internal.ConnectReplyDefaults() - reply.CollectTraces = tc.collectTraces - txn.appRun = newAppRun(cfg, reply) - - txn.Duration = tc.duration - if tc.synthetics { - txn.CrossProcess.Synthetics = &cat.SyntheticsHeader{} - txn.CrossProcess.SetSynthetics(tc.synthetics) - } - - if actual := txn.shouldSaveTrace(); actual != tc.expected { - t.Errorf("%s: unexpected shouldSaveTrace value; expected %v; got %v", tc.name, tc.expected, actual) - } - } -} - -func TestLazilyCalculateSampledTrue(t *testing.T) { - tx := &txn{} - tx.appRun = &appRun{} - tx.BetterCAT.Priority = 0.5 - tx.sampledCalculated = false - tx.BetterCAT.Enabled = true - tx.Reply = &internal.ConnectReply{ - AdaptiveSampler: internal.SampleEverything{}, - } - out := tx.lazilyCalculateSampled() - if !out || !tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 1.5 { - t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) - } - tx.Reply.AdaptiveSampler = internal.SampleNothing{} - out = tx.lazilyCalculateSampled() - if !out || !tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 1.5 { - t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) - } -} - -func TestLazilyCalculateSampledFalse(t *testing.T) { - tx := &txn{} - tx.appRun = &appRun{} - tx.BetterCAT.Priority = 0.5 - tx.sampledCalculated = false - tx.BetterCAT.Enabled = true - tx.Reply = &internal.ConnectReply{ - AdaptiveSampler: internal.SampleNothing{}, - } - out := tx.lazilyCalculateSampled() - if out || tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 { - t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) - } - tx.Reply.AdaptiveSampler = internal.SampleEverything{} - out = tx.lazilyCalculateSampled() - if out || tx.BetterCAT.Sampled || !tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 { - t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) - } -} - -func TestLazilyCalculateSampledCATDisabled(t *testing.T) { - tx := &txn{} - tx.appRun = &appRun{} - tx.BetterCAT.Priority = 0.5 - tx.sampledCalculated = false - tx.BetterCAT.Enabled = false - tx.Reply = &internal.ConnectReply{ - AdaptiveSampler: internal.SampleEverything{}, - } - out := tx.lazilyCalculateSampled() - if out || tx.BetterCAT.Sampled || tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 { - t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) - } - out = tx.lazilyCalculateSampled() - if out || tx.BetterCAT.Sampled || tx.sampledCalculated || tx.BetterCAT.Priority != 0.5 { - t.Error(out, tx.BetterCAT.Sampled, tx.sampledCalculated, tx.BetterCAT.Priority) - } -} - -type expectTxnTimes struct { - txn *txn - testName string - start time.Time - stop time.Time - duration time.Duration - totalTime time.Duration -} - -func TestTransactionDurationTotalTime(t *testing.T) { - // These tests touch internal txn structures rather than the public API: - // Testing duration and total time is tough because our API functions do - // not take fixed times. - start := time.Now() - testTxnTimes := func(expect expectTxnTimes) { - if expect.txn.Start != expect.start { - t.Error("start time", expect.testName, expect.txn.Start, expect.start) - } - if expect.txn.Stop != expect.stop { - t.Error("stop time", expect.testName, expect.txn.Stop, expect.stop) - } - if expect.txn.Duration != expect.duration { - t.Error("duration", expect.testName, expect.txn.Duration, expect.duration) - } - if expect.txn.TotalTime != expect.totalTime { - t.Error("total time", expect.testName, expect.txn.TotalTime, expect.totalTime) - } - } - - // Basic transaction with no async activity. - tx := &txn{} - tx.markStart(start) - segmentStart := internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(1*time.Second)) - internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(2*time.Second), "name") - tx.markEnd(start.Add(3*time.Second), &tx.mainThread) - testTxnTimes(expectTxnTimes{ - txn: tx, - testName: "basic transaction", - start: start, - stop: start.Add(3 * time.Second), - duration: 3 * time.Second, - totalTime: 3 * time.Second, - }) - - // Transaction with async activity. - tx = &txn{} - tx.markStart(start) - segmentStart = internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(1*time.Second)) - internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(2*time.Second), "name") - asyncThread := createThread(tx) - asyncSegmentStart := internal.StartSegment(&tx.TxnData, asyncThread, start.Add(1*time.Second)) - internal.EndBasicSegment(&tx.TxnData, asyncThread, asyncSegmentStart, start.Add(2*time.Second), "name") - tx.markEnd(start.Add(3*time.Second), &tx.mainThread) - testTxnTimes(expectTxnTimes{ - txn: tx, - testName: "transaction with async activity", - start: start, - stop: start.Add(3 * time.Second), - duration: 3 * time.Second, - totalTime: 4 * time.Second, - }) - - // Transaction ended on async thread. - tx = &txn{} - tx.markStart(start) - segmentStart = internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(1*time.Second)) - internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(2*time.Second), "name") - asyncThread = createThread(tx) - asyncSegmentStart = internal.StartSegment(&tx.TxnData, asyncThread, start.Add(1*time.Second)) - internal.EndBasicSegment(&tx.TxnData, asyncThread, asyncSegmentStart, start.Add(2*time.Second), "name") - tx.markEnd(start.Add(3*time.Second), asyncThread) - testTxnTimes(expectTxnTimes{ - txn: tx, - testName: "transaction ended on async thread", - start: start, - stop: start.Add(3 * time.Second), - duration: 3 * time.Second, - totalTime: 4 * time.Second, - }) - - // Duration exceeds TotalTime. - tx = &txn{} - tx.markStart(start) - segmentStart = internal.StartSegment(&tx.TxnData, &tx.mainThread, start.Add(0*time.Second)) - internal.EndBasicSegment(&tx.TxnData, &tx.mainThread, segmentStart, start.Add(1*time.Second), "name") - asyncThread = createThread(tx) - asyncSegmentStart = internal.StartSegment(&tx.TxnData, asyncThread, start.Add(2*time.Second)) - internal.EndBasicSegment(&tx.TxnData, asyncThread, asyncSegmentStart, start.Add(3*time.Second), "name") - tx.markEnd(start.Add(3*time.Second), asyncThread) - testTxnTimes(expectTxnTimes{ - txn: tx, - testName: "TotalTime should be at least Duration", - start: start, - stop: start.Add(3 * time.Second), - duration: 3 * time.Second, - totalTime: 3 * time.Second, - }) -} - -func TestGetTraceMetadataDistributedTracingDisabled(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - metadata := txn.GetTraceMetadata() - if metadata.SpanID != "" { - t.Error(metadata.SpanID) - } - if metadata.TraceID != "" { - t.Error(metadata.TraceID) - } -} - -func TestGetTraceMetadataSuccess(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - metadata := txn.GetTraceMetadata() - if metadata.SpanID != "bcfb32e050b264b8" { - t.Error(metadata.SpanID) - } - if metadata.TraceID != "d9466896a525ccbf" { - t.Error(metadata.TraceID) - } - StartSegment(txn, "name") - // Span id should be different now that a segment has started. - metadata = txn.GetTraceMetadata() - if metadata.SpanID != "0e97aeb2f79d5d27" { - t.Error(metadata.SpanID) - } - if metadata.TraceID != "d9466896a525ccbf" { - t.Error(metadata.TraceID) - } -} - -func TestGetTraceMetadataEnded(t *testing.T) { - // Test that GetTraceMetadata returns empty strings if the transaction - // has been finished. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - txn.End() - metadata := txn.GetTraceMetadata() - if metadata.SpanID != "" { - t.Error(metadata.SpanID) - } - if metadata.TraceID != "" { - t.Error(metadata.TraceID) - } -} - -func TestGetTraceMetadataNotSampled(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleNothing{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - metadata := txn.GetTraceMetadata() - if metadata.SpanID != "" { - t.Error(metadata.SpanID) - } - if metadata.TraceID != "d9466896a525ccbf" { - t.Error(metadata.TraceID) - } -} - -func TestGetTraceMetadataSpanEventsDisabled(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - cfg.SpanEvents.Enabled = false - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - metadata := txn.GetTraceMetadata() - if metadata.SpanID != "" { - t.Error(metadata.SpanID) - } - if metadata.TraceID != "d9466896a525ccbf" { - t.Error(metadata.TraceID) - } -} - -func TestGetTraceMetadataInboundPayload(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - reply.AccountID = "account-id" - reply.TrustedAccountKey = "trust-key" - reply.PrimaryAppID = "app-id" - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - payload := app.StartTransaction("hello", nil, nil).CreateDistributedTracePayload() - p := payload.(internal.Payload) - p.TracedID = "trace-id" - - txn := app.StartTransaction("hello", nil, nil) - err := txn.AcceptDistributedTracePayload(TransportHTTP, p) - if nil != err { - t.Error(err) - } - metadata := txn.GetTraceMetadata() - if metadata.SpanID != "9d2c19bd03daf755" { - t.Error(metadata.SpanID) - } - if metadata.TraceID != "trace-id" { - t.Error(metadata.TraceID) - } -} - -func TestGetLinkingMetadata(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - reply.EntityGUID = "entities-are-guid" - reply.TraceIDGenerator = internal.NewTraceIDGenerator(12345) - } - cfgfn := func(cfg *Config) { - cfg.AppName = "app-name" - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - - metadata := txn.GetLinkingMetadata() - host, _ := sysinfo.Hostname() - if metadata.TraceID != "d9466896a525ccbf" { - t.Error("wrong TraceID:", metadata.TraceID) - } - if metadata.SpanID != "bcfb32e050b264b8" { - t.Error("wrong SpanID:", metadata.SpanID) - } - if metadata.EntityName != "app-name" { - t.Error("wrong EntityName:", metadata.EntityName) - } - if metadata.EntityType != "SERVICE" { - t.Error("wrong EntityType:", metadata.EntityType) - } - if metadata.EntityGUID != "entities-are-guid" { - t.Error("wrong EntityGUID:", metadata.EntityGUID) - } - if metadata.Hostname != host { - t.Error("wrong Hostname:", metadata.Hostname) - } -} - -func TestGetLinkingMetadataAppNames(t *testing.T) { - testcases := []struct { - appName string - expected string - }{ - {appName: "one-name", expected: "one-name"}, - {appName: "one-name;two-name;three-name", expected: "one-name"}, - {appName: "", expected: ""}, - } - - for _, test := range testcases { - cfgfn := func(cfg *Config) { - cfg.AppName = test.appName - } - app := testApp(nil, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - - metadata := txn.GetLinkingMetadata() - if metadata.EntityName != test.expected { - t.Errorf("wrong EntityName, actual=%s expected=%s", metadata.EntityName, test.expected) - } - } -} - -func TestIsSampledFalse(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleNothing{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - sampled := txn.IsSampled() - if sampled == true { - t.Error("txn should not be sampled") - } -} - -func TestIsSampledTrue(t *testing.T) { - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - sampled := txn.IsSampled() - if sampled == false { - t.Error("txn should be sampled") - } -} - -func TestIsSampledEnded(t *testing.T) { - // Test that Transaction.IsSampled returns false if the transaction has - // already ended. - replyfn := func(reply *internal.ConnectReply) { - reply.AdaptiveSampler = internal.SampleEverything{} - } - cfgfn := func(cfg *Config) { - cfg.DistributedTracer.Enabled = true - } - app := testApp(replyfn, cfgfn, t) - txn := app.StartTransaction("hello", nil, nil) - txn.End() - sampled := txn.IsSampled() - if sampled == true { - t.Error("finished txn should not be sampled") - } -} diff --git a/log.go b/log.go deleted file mode 100644 index d75a8d079..000000000 --- a/log.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "io" - - "github.com/newrelic/go-agent/internal/logger" -) - -// Logger is the interface that is used for logging in the go-agent. Assign the -// Config.Logger field to the Logger you wish to use. Loggers must be safe for -// use in multiple goroutines. Two Logger implementations are included: -// NewLogger, which logs at info level, and NewDebugLogger which logs at debug -// level. logrus and logxi are supported by the integration packages -// https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlogrus and -// https://godoc.org/github.com/newrelic/go-agent/_integrations/nrlogxi/v1. -type Logger interface { - Error(msg string, context map[string]interface{}) - Warn(msg string, context map[string]interface{}) - Info(msg string, context map[string]interface{}) - Debug(msg string, context map[string]interface{}) - DebugEnabled() bool -} - -// NewLogger creates a basic Logger at info level. -func NewLogger(w io.Writer) Logger { - return logger.New(w, false) -} - -// NewDebugLogger creates a basic Logger at debug level. -func NewDebugLogger(w io.Writer) Logger { - return logger.New(w, true) -} diff --git a/segments.go b/segments.go deleted file mode 100644 index 5fc8ad454..000000000 --- a/segments.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "net/http" -) - -// SegmentStartTime is created by Transaction.StartSegmentNow and marks the -// beginning of a segment. A segment with a zero-valued SegmentStartTime may -// safely be ended. -type SegmentStartTime struct{ segment } - -// Segment is used to instrument functions, methods, and blocks of code. The -// easiest way use Segment is the StartSegment function. -type Segment struct { - StartTime SegmentStartTime - Name string -} - -// DatastoreSegment is used to instrument calls to databases and object stores. -type DatastoreSegment struct { - // StartTime should be assigned using StartSegmentNow before each datastore - // call is made. - StartTime SegmentStartTime - - // Product, Collection, and Operation are highly recommended as they are - // used for aggregate metrics: - // - // Product is the datastore type. See the constants in - // https://github.com/newrelic/go-agent/blob/master/datastore.go. Product - // is one of the fields primarily responsible for the grouping of Datastore - // metrics. - Product DatastoreProduct - // Collection is the table or group being operated upon in the datastore, - // e.g. "users_table". This becomes the db.collection attribute on Span - // events and Transaction Trace segments. Collection is one of the fields - // primarily responsible for the grouping of Datastore metrics. - Collection string - // Operation is the relevant action, e.g. "SELECT" or "GET". Operation is - // one of the fields primarily responsible for the grouping of Datastore - // metrics. - Operation string - - // The following fields are used for extra metrics and added to instance - // data: - // - // ParameterizedQuery may be set to the query being performed. It must - // not contain any raw parameters, only placeholders. - ParameterizedQuery string - // QueryParameters may be used to provide query parameters. Care should - // be taken to only provide parameters which are not sensitive. - // QueryParameters are ignored in high security mode. The keys must contain - // fewer than than 255 bytes. The values must be numbers, strings, or - // booleans. - QueryParameters map[string]interface{} - // Host is the name of the server hosting the datastore. - Host string - // PortPathOrID can represent either the port, path, or id of the - // datastore being connected to. - PortPathOrID string - // DatabaseName is name of database instance where the current query is - // being executed. This becomes the db.instance attribute on Span events - // and Transaction Trace segments. - DatabaseName string -} - -// ExternalSegment instruments external calls. StartExternalSegment is the -// recommended way to create ExternalSegments. -type ExternalSegment struct { - StartTime SegmentStartTime - Request *http.Request - Response *http.Response - - // URL is an optional field which can be populated in lieu of Request if - // you don't have an http.Request. Either URL or Request must be - // populated. If both are populated then Request information takes - // priority. URL is parsed using url.Parse so it must include the - // protocol scheme (eg. "http://"). - URL string - // Host is an optional field that is automatically populated from the - // Request or URL. It is used for external metrics, transaction trace - // segment names, and span event names. Use this field to override the - // host in the URL or Request. This field does not override the host in - // the "http.url" attribute. - Host string - // Procedure is an optional field that can be set to the remote - // procedure being called. If set, this value will be used in metrics, - // transaction trace segment names, and span event names. If unset, the - // request's http method is used. - Procedure string - // Library is an optional field that defaults to "http". It is used for - // external metrics and the "component" span attribute. It should be - // the framework making the external call. - Library string -} - -// MessageProducerSegment instruments calls to add messages to a queueing system. -type MessageProducerSegment struct { - StartTime SegmentStartTime - - // Library is the name of the library instrumented. eg. "RabbitMQ", - // "JMS" - Library string - - // DestinationType is the destination type. - DestinationType MessageDestinationType - - // DestinationName is the name of your queue or topic. eg. "UsersQueue". - DestinationName string - - // DestinationTemporary must be set to true if destination is temporary - // to improve metric grouping. - DestinationTemporary bool -} - -// MessageDestinationType is used for the MessageSegment.DestinationType field. -type MessageDestinationType string - -// These message destination type constants are used in for the -// MessageSegment.DestinationType field. -const ( - MessageQueue MessageDestinationType = "Queue" - MessageTopic MessageDestinationType = "Topic" - MessageExchange MessageDestinationType = "Exchange" -) - -// End finishes the segment. -func (s *Segment) End() error { return endSegment(s) } - -// End finishes the datastore segment. -func (s *DatastoreSegment) End() error { return endDatastore(s) } - -// End finishes the external segment. -func (s *ExternalSegment) End() error { return endExternal(s) } - -// End finishes the message segment. -func (s *MessageProducerSegment) End() error { return endMessage(s) } - -// OutboundHeaders returns the headers that should be attached to the external -// request. -func (s *ExternalSegment) OutboundHeaders() http.Header { - return outboundHeaders(s) -} - -// StartSegmentNow starts timing a segment. This function is recommended over -// Transaction.StartSegmentNow() because it is nil safe. -func StartSegmentNow(txn Transaction) SegmentStartTime { - if nil != txn { - return txn.StartSegmentNow() - } - return SegmentStartTime{} -} - -// StartSegment makes it easy to instrument segments. To time a function, do -// the following: -// -// func timeMe(txn newrelic.Transaction) { -// defer newrelic.StartSegment(txn, "timeMe").End() -// // ... function code here ... -// } -// -// To time a block of code, do the following: -// -// segment := StartSegment(txn, "myBlock") -// // ... code you want to time here ... -// segment.End() -// -func StartSegment(txn Transaction, name string) *Segment { - return &Segment{ - StartTime: StartSegmentNow(txn), - Name: name, - } -} - -// StartExternalSegment starts the instrumentation of an external call and adds -// distributed tracing headers to the request. If the Transaction parameter is -// nil then StartExternalSegment will look for a Transaction in the request's -// context using FromContext. -// -// Using the same http.Client for all of your external requests? Check out -// NewRoundTripper: You may not need to use StartExternalSegment at all! -// -func StartExternalSegment(txn Transaction, request *http.Request) *ExternalSegment { - if nil == txn { - txn = transactionFromRequestContext(request) - } - s := &ExternalSegment{ - StartTime: StartSegmentNow(txn), - Request: request, - } - - if request != nil && request.Header != nil { - for key, values := range s.OutboundHeaders() { - for _, value := range values { - request.Header.Add(key, value) - } - } - } - - return s -} diff --git a/sql_driver.go b/sql_driver.go deleted file mode 100644 index 2d923edad..000000000 --- a/sql_driver.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.10 - -package newrelic - -import ( - "context" - "database/sql/driver" -) - -// SQLDriverSegmentBuilder populates DatastoreSegments for sql.Driver -// instrumentation. Use this to instrument a database that is not supported by -// an existing integration package (nrmysql, nrpq, and nrsqlite3). See -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrmysql/nrmysql.go -// for example use. -type SQLDriverSegmentBuilder struct { - BaseSegment DatastoreSegment - ParseQuery func(segment *DatastoreSegment, query string) - ParseDSN func(segment *DatastoreSegment, dataSourceName string) -} - -// InstrumentSQLDriver wraps a driver.Driver, adding instrumentation for exec -// and query calls made with a transaction-containing context. Use this to -// instrument a database driver that is not supported by an existing integration -// package (nrmysql, nrpq, and nrsqlite3). See -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrmysql/nrmysql.go -// for example use. -func InstrumentSQLDriver(d driver.Driver, bld SQLDriverSegmentBuilder) driver.Driver { - return optionalMethodsDriver(&wrapDriver{bld: bld, original: d}) -} - -// InstrumentSQLConnector wraps a driver.Connector, adding instrumentation for -// exec and query calls made with a transaction-containing context. Use this to -// instrument a database connector that is not supported by an existing -// integration package (nrmysql, nrpq, and nrsqlite3). See -// https://github.com/newrelic/go-agent/blob/master/_integrations/nrmysql/nrmysql.go -// for example use. -func InstrumentSQLConnector(connector driver.Connector, bld SQLDriverSegmentBuilder) driver.Connector { - return &wrapConnector{original: connector, bld: bld} -} - -func (bld SQLDriverSegmentBuilder) useDSN(dsn string) SQLDriverSegmentBuilder { - if f := bld.ParseDSN; nil != f { - f(&bld.BaseSegment, dsn) - } - return bld -} - -func (bld SQLDriverSegmentBuilder) useQuery(query string) SQLDriverSegmentBuilder { - if f := bld.ParseQuery; nil != f { - f(&bld.BaseSegment, query) - } - return bld -} - -func (bld SQLDriverSegmentBuilder) startSegment(ctx context.Context) DatastoreSegment { - segment := bld.BaseSegment - segment.StartTime = StartSegmentNow(FromContext(ctx)) - return segment -} - -type wrapDriver struct { - bld SQLDriverSegmentBuilder - original driver.Driver -} - -type wrapConnector struct { - bld SQLDriverSegmentBuilder - original driver.Connector -} - -type wrapConn struct { - bld SQLDriverSegmentBuilder - original driver.Conn -} - -type wrapStmt struct { - bld SQLDriverSegmentBuilder - original driver.Stmt -} - -func (w *wrapDriver) Open(name string) (driver.Conn, error) { - original, err := w.original.Open(name) - if err != nil { - return nil, err - } - return optionalMethodsConn(&wrapConn{ - original: original, - bld: w.bld.useDSN(name), - }), nil -} - -// OpenConnector implements DriverContext. -func (w *wrapDriver) OpenConnector(name string) (driver.Connector, error) { - original, err := w.original.(driver.DriverContext).OpenConnector(name) - if err != nil { - return nil, err - } - return &wrapConnector{ - original: original, - bld: w.bld.useDSN(name), - }, nil -} - -func (w *wrapConnector) Connect(ctx context.Context) (driver.Conn, error) { - original, err := w.original.Connect(ctx) - if nil != err { - return nil, err - } - return optionalMethodsConn(&wrapConn{ - bld: w.bld, - original: original, - }), nil -} - -func (w *wrapConnector) Driver() driver.Driver { - return optionalMethodsDriver(&wrapDriver{ - bld: w.bld, - original: w.original.Driver(), - }) -} - -func prepare(original driver.Stmt, err error, bld SQLDriverSegmentBuilder, query string) (driver.Stmt, error) { - if nil != err { - return nil, err - } - return optionalMethodsStmt(&wrapStmt{ - bld: bld.useQuery(query), - original: original, - }), nil -} - -func (w *wrapConn) Prepare(query string) (driver.Stmt, error) { - original, err := w.original.Prepare(query) - return prepare(original, err, w.bld, query) -} - -// PrepareContext implements ConnPrepareContext. -func (w *wrapConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { - original, err := w.original.(driver.ConnPrepareContext).PrepareContext(ctx, query) - return prepare(original, err, w.bld, query) -} - -func (w *wrapConn) Close() error { - return w.original.Close() -} - -func (w *wrapConn) Begin() (driver.Tx, error) { - return w.original.Begin() -} - -// BeginTx implements ConnBeginTx. -func (w *wrapConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { - return w.original.(driver.ConnBeginTx).BeginTx(ctx, opts) -} - -// Exec implements Execer. -func (w *wrapConn) Exec(query string, args []driver.Value) (driver.Result, error) { - return w.original.(driver.Execer).Exec(query, args) -} - -// ExecContext implements ExecerContext. -func (w *wrapConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { - segment := w.bld.useQuery(query).startSegment(ctx) - result, err := w.original.(driver.ExecerContext).ExecContext(ctx, query, args) - if err != driver.ErrSkip { - segment.End() - } - return result, err -} - -// CheckNamedValue implements NamedValueChecker. -func (w *wrapConn) CheckNamedValue(v *driver.NamedValue) error { - return w.original.(driver.NamedValueChecker).CheckNamedValue(v) -} - -// Ping implements Pinger. -func (w *wrapConn) Ping(ctx context.Context) error { - return w.original.(driver.Pinger).Ping(ctx) -} - -func (w *wrapConn) Query(query string, args []driver.Value) (driver.Rows, error) { - return w.original.(driver.Queryer).Query(query, args) -} - -// QueryContext implements QueryerContext. -func (w *wrapConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { - segment := w.bld.useQuery(query).startSegment(ctx) - rows, err := w.original.(driver.QueryerContext).QueryContext(ctx, query, args) - if err != driver.ErrSkip { - segment.End() - } - return rows, err -} - -// ResetSession implements SessionResetter. -func (w *wrapConn) ResetSession(ctx context.Context) error { - return w.original.(driver.SessionResetter).ResetSession(ctx) -} - -func (w *wrapStmt) Close() error { - return w.original.Close() -} - -func (w *wrapStmt) NumInput() int { - return w.original.NumInput() -} - -func (w *wrapStmt) Exec(args []driver.Value) (driver.Result, error) { - return w.original.Exec(args) -} - -func (w *wrapStmt) Query(args []driver.Value) (driver.Rows, error) { - return w.original.Query(args) -} - -// ColumnConverter implements ColumnConverter. -func (w *wrapStmt) ColumnConverter(idx int) driver.ValueConverter { - return w.original.(driver.ColumnConverter).ColumnConverter(idx) -} - -// CheckNamedValue implements NamedValueChecker. -func (w *wrapStmt) CheckNamedValue(v *driver.NamedValue) error { - return w.original.(driver.NamedValueChecker).CheckNamedValue(v) -} - -// ExecContext implements StmtExecContext. -func (w *wrapStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { - segment := w.bld.startSegment(ctx) - result, err := w.original.(driver.StmtExecContext).ExecContext(ctx, args) - segment.End() - return result, err -} - -// QueryContext implements StmtQueryContext. -func (w *wrapStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { - segment := w.bld.startSegment(ctx) - rows, err := w.original.(driver.StmtQueryContext).QueryContext(ctx, args) - segment.End() - return rows, err -} - -var ( - _ interface { - driver.Driver - driver.DriverContext - } = &wrapDriver{} - _ interface { - driver.Connector - } = &wrapConnector{} - _ interface { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - } = &wrapConn{} - _ interface { - driver.Stmt - driver.ColumnConverter - driver.NamedValueChecker - driver.StmtExecContext - driver.StmtQueryContext - } = &wrapStmt{} -) diff --git a/sql_driver_optional_methods.go b/sql_driver_optional_methods.go deleted file mode 100644 index 2c9d173c7..000000000 --- a/sql_driver_optional_methods.go +++ /dev/null @@ -1,2243 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.10 - -package newrelic - -import "database/sql/driver" - -func optionalMethodsDriver(dv *wrapDriver) driver.Driver { - // GENERATED CODE DO NOT MODIFY - // This code generated by internal/tools/interface-wrapping - var ( - i0 int32 = 1 << 0 - ) - var interfaceSet int32 - if _, ok := dv.original.(driver.DriverContext); ok { - interfaceSet |= i0 - } - switch interfaceSet { - default: // No optional interfaces implemented - return struct { - driver.Driver - }{dv} - case i0: - return struct { - driver.Driver - driver.DriverContext - }{dv, dv} - } -} - -func optionalMethodsStmt(stmt *wrapStmt) driver.Stmt { - // GENERATED CODE DO NOT MODIFY - // This code generated by internal/tools/interface-wrapping - var ( - i0 int32 = 1 << 0 - i1 int32 = 1 << 1 - i2 int32 = 1 << 2 - i3 int32 = 1 << 3 - ) - var interfaceSet int32 - if _, ok := stmt.original.(driver.ColumnConverter); ok { - interfaceSet |= i0 - } - if _, ok := stmt.original.(driver.NamedValueChecker); ok { - interfaceSet |= i1 - } - if _, ok := stmt.original.(driver.StmtExecContext); ok { - interfaceSet |= i2 - } - if _, ok := stmt.original.(driver.StmtQueryContext); ok { - interfaceSet |= i3 - } - switch interfaceSet { - default: // No optional interfaces implemented - return struct { - driver.Stmt - }{stmt} - case i0: - return struct { - driver.Stmt - driver.ColumnConverter - }{stmt, stmt} - case i1: - return struct { - driver.Stmt - driver.NamedValueChecker - }{stmt, stmt} - case i0 | i1: - return struct { - driver.Stmt - driver.ColumnConverter - driver.NamedValueChecker - }{stmt, stmt, stmt} - case i2: - return struct { - driver.Stmt - driver.StmtExecContext - }{stmt, stmt} - case i0 | i2: - return struct { - driver.Stmt - driver.ColumnConverter - driver.StmtExecContext - }{stmt, stmt, stmt} - case i1 | i2: - return struct { - driver.Stmt - driver.NamedValueChecker - driver.StmtExecContext - }{stmt, stmt, stmt} - case i0 | i1 | i2: - return struct { - driver.Stmt - driver.ColumnConverter - driver.NamedValueChecker - driver.StmtExecContext - }{stmt, stmt, stmt, stmt} - case i3: - return struct { - driver.Stmt - driver.StmtQueryContext - }{stmt, stmt} - case i0 | i3: - return struct { - driver.Stmt - driver.ColumnConverter - driver.StmtQueryContext - }{stmt, stmt, stmt} - case i1 | i3: - return struct { - driver.Stmt - driver.NamedValueChecker - driver.StmtQueryContext - }{stmt, stmt, stmt} - case i0 | i1 | i3: - return struct { - driver.Stmt - driver.ColumnConverter - driver.NamedValueChecker - driver.StmtQueryContext - }{stmt, stmt, stmt, stmt} - case i2 | i3: - return struct { - driver.Stmt - driver.StmtExecContext - driver.StmtQueryContext - }{stmt, stmt, stmt} - case i0 | i2 | i3: - return struct { - driver.Stmt - driver.ColumnConverter - driver.StmtExecContext - driver.StmtQueryContext - }{stmt, stmt, stmt, stmt} - case i1 | i2 | i3: - return struct { - driver.Stmt - driver.NamedValueChecker - driver.StmtExecContext - driver.StmtQueryContext - }{stmt, stmt, stmt, stmt} - case i0 | i1 | i2 | i3: - return struct { - driver.Stmt - driver.ColumnConverter - driver.NamedValueChecker - driver.StmtExecContext - driver.StmtQueryContext - }{stmt, stmt, stmt, stmt, stmt} - } -} - -func optionalMethodsConn(conn *wrapConn) driver.Conn { - // GENERATED CODE DO NOT MODIFY - // This code generated by internal/tools/interface-wrapping - var ( - i0 int32 = 1 << 0 - i1 int32 = 1 << 1 - i2 int32 = 1 << 2 - i3 int32 = 1 << 3 - i4 int32 = 1 << 4 - i5 int32 = 1 << 5 - i6 int32 = 1 << 6 - i7 int32 = 1 << 7 - ) - var interfaceSet int32 - if _, ok := conn.original.(driver.ConnBeginTx); ok { - interfaceSet |= i0 - } - if _, ok := conn.original.(driver.ConnPrepareContext); ok { - interfaceSet |= i1 - } - if _, ok := conn.original.(driver.Execer); ok { - interfaceSet |= i2 - } - if _, ok := conn.original.(driver.ExecerContext); ok { - interfaceSet |= i3 - } - if _, ok := conn.original.(driver.NamedValueChecker); ok { - interfaceSet |= i4 - } - if _, ok := conn.original.(driver.Pinger); ok { - interfaceSet |= i5 - } - if _, ok := conn.original.(driver.Queryer); ok { - interfaceSet |= i6 - } - if _, ok := conn.original.(driver.QueryerContext); ok { - interfaceSet |= i7 - } - switch interfaceSet { - default: // No optional interfaces implemented - return struct { - driver.Conn - }{conn} - case i0: - return struct { - driver.Conn - driver.ConnBeginTx - }{conn, conn} - case i1: - return struct { - driver.Conn - driver.ConnPrepareContext - }{conn, conn} - case i0 | i1: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - }{conn, conn, conn} - case i2: - return struct { - driver.Conn - driver.Execer - }{conn, conn} - case i0 | i2: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - }{conn, conn, conn} - case i1 | i2: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - }{conn, conn, conn} - case i0 | i1 | i2: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - }{conn, conn, conn, conn} - case i3: - return struct { - driver.Conn - driver.ExecerContext - }{conn, conn} - case i0 | i3: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - }{conn, conn, conn} - case i1 | i3: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - }{conn, conn, conn} - case i0 | i1 | i3: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - }{conn, conn, conn, conn} - case i2 | i3: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - }{conn, conn, conn} - case i0 | i2 | i3: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - }{conn, conn, conn, conn} - case i1 | i2 | i3: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - }{conn, conn, conn, conn} - case i0 | i1 | i2 | i3: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - }{conn, conn, conn, conn, conn} - case i4: - return struct { - driver.Conn - driver.NamedValueChecker - }{conn, conn} - case i0 | i4: - return struct { - driver.Conn - driver.ConnBeginTx - driver.NamedValueChecker - }{conn, conn, conn} - case i1 | i4: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.NamedValueChecker - }{conn, conn, conn} - case i0 | i1 | i4: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.NamedValueChecker - }{conn, conn, conn, conn} - case i2 | i4: - return struct { - driver.Conn - driver.Execer - driver.NamedValueChecker - }{conn, conn, conn} - case i0 | i2 | i4: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.NamedValueChecker - }{conn, conn, conn, conn} - case i1 | i2 | i4: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - }{conn, conn, conn, conn} - case i0 | i1 | i2 | i4: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - }{conn, conn, conn, conn, conn} - case i3 | i4: - return struct { - driver.Conn - driver.ExecerContext - driver.NamedValueChecker - }{conn, conn, conn} - case i0 | i3 | i4: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.NamedValueChecker - }{conn, conn, conn, conn} - case i1 | i3 | i4: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - }{conn, conn, conn, conn} - case i0 | i1 | i3 | i4: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - }{conn, conn, conn, conn, conn} - case i2 | i3 | i4: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - }{conn, conn, conn, conn} - case i0 | i2 | i3 | i4: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - }{conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i4: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i4: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - }{conn, conn, conn, conn, conn, conn} - case i5: - return struct { - driver.Conn - driver.Pinger - }{conn, conn} - case i0 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Pinger - }{conn, conn, conn} - case i1 | i5: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Pinger - }{conn, conn, conn} - case i0 | i1 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Pinger - }{conn, conn, conn, conn} - case i2 | i5: - return struct { - driver.Conn - driver.Execer - driver.Pinger - }{conn, conn, conn} - case i0 | i2 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.Pinger - }{conn, conn, conn, conn} - case i1 | i2 | i5: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.Pinger - }{conn, conn, conn, conn} - case i0 | i1 | i2 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.Pinger - }{conn, conn, conn, conn, conn} - case i3 | i5: - return struct { - driver.Conn - driver.ExecerContext - driver.Pinger - }{conn, conn, conn} - case i0 | i3 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.Pinger - }{conn, conn, conn, conn} - case i1 | i3 | i5: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.Pinger - }{conn, conn, conn, conn} - case i0 | i1 | i3 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.Pinger - }{conn, conn, conn, conn, conn} - case i2 | i3 | i5: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.Pinger - }{conn, conn, conn, conn} - case i0 | i2 | i3 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.Pinger - }{conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i5: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Pinger - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Pinger - }{conn, conn, conn, conn, conn, conn} - case i4 | i5: - return struct { - driver.Conn - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn} - case i0 | i4 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn} - case i1 | i4 | i5: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn} - case i0 | i1 | i4 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn} - case i2 | i4 | i5: - return struct { - driver.Conn - driver.Execer - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn} - case i0 | i2 | i4 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn} - case i1 | i2 | i4 | i5: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i4 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn, conn} - case i3 | i4 | i5: - return struct { - driver.Conn - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn} - case i0 | i3 | i4 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn} - case i1 | i3 | i4 | i5: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i4 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn, conn} - case i2 | i3 | i4 | i5: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i4 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i4 | i5: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i4 | i5: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - }{conn, conn, conn, conn, conn, conn, conn} - case i6: - return struct { - driver.Conn - driver.Queryer - }{conn, conn} - case i0 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Queryer - }{conn, conn, conn} - case i1 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Queryer - }{conn, conn, conn} - case i0 | i1 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Queryer - }{conn, conn, conn, conn} - case i2 | i6: - return struct { - driver.Conn - driver.Execer - driver.Queryer - }{conn, conn, conn} - case i0 | i2 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.Queryer - }{conn, conn, conn, conn} - case i1 | i2 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i1 | i2 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.Queryer - }{conn, conn, conn, conn, conn} - case i3 | i6: - return struct { - driver.Conn - driver.ExecerContext - driver.Queryer - }{conn, conn, conn} - case i0 | i3 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.Queryer - }{conn, conn, conn, conn} - case i1 | i3 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i1 | i3 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.Queryer - }{conn, conn, conn, conn, conn} - case i2 | i3 | i6: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i2 | i3 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.Queryer - }{conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i4 | i6: - return struct { - driver.Conn - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn} - case i0 | i4 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn} - case i1 | i4 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i1 | i4 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn} - case i2 | i4 | i6: - return struct { - driver.Conn - driver.Execer - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i2 | i4 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn} - case i1 | i2 | i4 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i4 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i3 | i4 | i6: - return struct { - driver.Conn - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i3 | i4 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn} - case i1 | i3 | i4 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i4 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i2 | i3 | i4 | i6: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i4 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i4 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i4 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - }{conn, conn, conn, conn, conn, conn, conn} - case i5 | i6: - return struct { - driver.Conn - driver.Pinger - driver.Queryer - }{conn, conn, conn} - case i0 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn} - case i1 | i5 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i1 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i2 | i5 | i6: - return struct { - driver.Conn - driver.Execer - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i2 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i1 | i2 | i5 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i3 | i5 | i6: - return struct { - driver.Conn - driver.ExecerContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i3 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i1 | i3 | i5 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i2 | i3 | i5 | i6: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i5 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn, conn} - case i4 | i5 | i6: - return struct { - driver.Conn - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn} - case i0 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i1 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i1 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i2 | i4 | i5 | i6: - return struct { - driver.Conn - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i2 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn, conn} - case i3 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn} - case i0 | i3 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i1 | i3 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn, conn} - case i2 | i3 | i4 | i5 | i6: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i4 | i5 | i6: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - }{conn, conn, conn, conn, conn, conn, conn, conn} - case i7: - return struct { - driver.Conn - driver.QueryerContext - }{conn, conn} - case i0 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.QueryerContext - }{conn, conn, conn} - case i1 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.QueryerContext - }{conn, conn, conn} - case i0 | i1 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.QueryerContext - }{conn, conn, conn, conn} - case i2 | i7: - return struct { - driver.Conn - driver.Execer - driver.QueryerContext - }{conn, conn, conn} - case i0 | i2 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.QueryerContext - }{conn, conn, conn, conn} - case i1 | i2 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i1 | i2 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i3 | i7: - return struct { - driver.Conn - driver.ExecerContext - driver.QueryerContext - }{conn, conn, conn} - case i0 | i3 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.QueryerContext - }{conn, conn, conn, conn} - case i1 | i3 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i1 | i3 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i2 | i3 | i7: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i2 | i3 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i4 | i7: - return struct { - driver.Conn - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn} - case i0 | i4 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn} - case i1 | i4 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i1 | i4 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i2 | i4 | i7: - return struct { - driver.Conn - driver.Execer - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i2 | i4 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i2 | i4 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i4 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i3 | i4 | i7: - return struct { - driver.Conn - driver.ExecerContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i3 | i4 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i3 | i4 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i4 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i2 | i3 | i4 | i7: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i4 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i4 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i4 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i5 | i7: - return struct { - driver.Conn - driver.Pinger - driver.QueryerContext - }{conn, conn, conn} - case i0 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn} - case i1 | i5 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i1 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i2 | i5 | i7: - return struct { - driver.Conn - driver.Execer - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i2 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i2 | i5 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i3 | i5 | i7: - return struct { - driver.Conn - driver.ExecerContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i3 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i3 | i5 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i2 | i3 | i5 | i7: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i5 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i4 | i5 | i7: - return struct { - driver.Conn - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i2 | i4 | i5 | i7: - return struct { - driver.Conn - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i2 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i3 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i3 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i3 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i2 | i3 | i4 | i5 | i7: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i4 | i5 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn, conn} - case i6 | i7: - return struct { - driver.Conn - driver.Queryer - driver.QueryerContext - }{conn, conn, conn} - case i0 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn} - case i1 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i1 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i2 | i6 | i7: - return struct { - driver.Conn - driver.Execer - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i2 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i2 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i3 | i6 | i7: - return struct { - driver.Conn - driver.ExecerContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i3 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i3 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i2 | i3 | i6 | i7: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i4 | i6 | i7: - return struct { - driver.Conn - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i2 | i4 | i6 | i7: - return struct { - driver.Conn - driver.Execer - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i2 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i3 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i3 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i3 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i2 | i3 | i4 | i6 | i7: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i4 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn, conn} - case i5 | i6 | i7: - return struct { - driver.Conn - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn} - case i0 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i1 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i1 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i2 | i5 | i6 | i7: - return struct { - driver.Conn - driver.Execer - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i2 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i2 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i3 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ExecerContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i3 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i3 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i2 | i3 | i5 | i6 | i7: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn, conn} - case i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn} - case i0 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i1 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i1 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i2 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i2 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i1 | i2 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn, conn} - case i3 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn} - case i0 | i3 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i1 | i3 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i0 | i1 | i3 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn, conn} - case i2 | i3 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn} - case i0 | i2 | i3 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn, conn} - case i1 | i2 | i3 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn, conn} - case i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7: - return struct { - driver.Conn - driver.ConnBeginTx - driver.ConnPrepareContext - driver.Execer - driver.ExecerContext - driver.NamedValueChecker - driver.Pinger - driver.Queryer - driver.QueryerContext - }{conn, conn, conn, conn, conn, conn, conn, conn, conn} - } -} diff --git a/sql_driver_test.go b/sql_driver_test.go deleted file mode 100644 index fd79ab187..000000000 --- a/sql_driver_test.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// +build go1.10 - -package newrelic - -import ( - "context" - "database/sql/driver" - "strings" - "testing" - - "github.com/newrelic/go-agent/internal" -) - -var ( - driverTestMetrics = []internal.WantMetric{ - {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, - {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/MySQL/allOther", Scope: "", Forced: true, Data: nil}, - {Name: "Datastore/operation/MySQL/myoperation", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/mycollection/myoperation", Scope: "", Forced: false, Data: nil}, - {Name: "Datastore/statement/MySQL/mycollection/myoperation", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, - {Name: "Datastore/instance/MySQL/myhost/myport", Scope: "", Forced: false, Data: nil}, - } -) - -type testDriver struct{} -type testConnector struct{} -type testConn struct{} -type testStmt struct{} - -func (d testDriver) OpenConnector(name string) (driver.Connector, error) { return testConnector{}, nil } -func (d testDriver) Open(name string) (driver.Conn, error) { return testConn{}, nil } - -func (c testConnector) Connect(context.Context) (driver.Conn, error) { return testConn{}, nil } -func (c testConnector) Driver() driver.Driver { return testDriver{} } - -func (c testConn) Prepare(query string) (driver.Stmt, error) { return testStmt{}, nil } -func (c testConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { - return testStmt{}, nil -} -func (c testConn) Close() error { return nil } -func (c testConn) Begin() (driver.Tx, error) { return nil, nil } -func (c testConn) ExecContext(context.Context, string, []driver.NamedValue) (driver.Result, error) { - return nil, nil -} -func (c testConn) QueryContext(context.Context, string, []driver.NamedValue) (driver.Rows, error) { - return nil, nil -} - -func (s testStmt) Close() error { - return nil -} -func (s testStmt) NumInput() int { - return 0 -} -func (s testStmt) Exec(args []driver.Value) (driver.Result, error) { - return nil, nil -} -func (s testStmt) Query(args []driver.Value) (driver.Rows, error) { - return nil, nil -} -func (s testStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { - return nil, nil -} -func (s testStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { - return nil, nil -} - -var ( - testBuilder = SQLDriverSegmentBuilder{ - BaseSegment: DatastoreSegment{ - Product: DatastoreMySQL, - }, - ParseDSN: func(segment *DatastoreSegment, dsn string) { - fields := strings.Split(dsn, ",") - segment.Host = fields[0] - segment.PortPathOrID = fields[1] - segment.DatabaseName = fields[2] - }, - ParseQuery: func(segment *DatastoreSegment, query string) { - fields := strings.Split(query, ",") - segment.Operation = fields[0] - segment.Collection = fields[1] - }, - } -) - -func TestDriverStmtExecContext(t *testing.T) { - // Test that driver.Stmt.ExecContext calls get instrumented. - app := testApp(nil, nil, t) - dr := InstrumentSQLDriver(testDriver{}, testBuilder) - txn := app.StartTransaction("hello", nil, nil) - conn, _ := dr.Open("myhost,myport,mydatabase") - stmt, _ := conn.Prepare("myoperation,mycollection") - ctx := NewContext(context.Background(), txn) - stmt.(driver.StmtExecContext).ExecContext(ctx, nil) - txn.End() - app.ExpectMetrics(t, driverTestMetrics) -} - -func TestDriverStmtQueryContext(t *testing.T) { - // Test that driver.Stmt.PrepareContext calls get instrumented. - app := testApp(nil, nil, t) - dr := InstrumentSQLDriver(testDriver{}, testBuilder) - txn := app.StartTransaction("hello", nil, nil) - conn, _ := dr.Open("myhost,myport,mydatabase") - stmt, _ := conn.(driver.ConnPrepareContext).PrepareContext(context.Background(), "myoperation,mycollection") - ctx := NewContext(context.Background(), txn) - stmt.(driver.StmtQueryContext).QueryContext(ctx, nil) - txn.End() - app.ExpectMetrics(t, driverTestMetrics) -} - -func TestDriverConnExecContext(t *testing.T) { - // Test that driver.Conn.ExecContext calls get instrumented. - app := testApp(nil, nil, t) - dr := InstrumentSQLDriver(testDriver{}, testBuilder) - txn := app.StartTransaction("hello", nil, nil) - conn, _ := dr.Open("myhost,myport,mydatabase") - ctx := NewContext(context.Background(), txn) - conn.(driver.ExecerContext).ExecContext(ctx, "myoperation,mycollection", nil) - txn.End() - app.ExpectMetrics(t, driverTestMetrics) -} - -func TestDriverConnQueryContext(t *testing.T) { - // Test that driver.Conn.QueryContext calls get instrumented. - app := testApp(nil, nil, t) - dr := InstrumentSQLDriver(testDriver{}, testBuilder) - txn := app.StartTransaction("hello", nil, nil) - conn, _ := dr.Open("myhost,myport,mydatabase") - ctx := NewContext(context.Background(), txn) - conn.(driver.QueryerContext).QueryContext(ctx, "myoperation,mycollection", nil) - txn.End() - app.ExpectMetrics(t, driverTestMetrics) -} - -func TestDriverContext(t *testing.T) { - // Test that driver.OpenConnector returns an instrumented connector. - app := testApp(nil, nil, t) - dr := InstrumentSQLDriver(testDriver{}, testBuilder) - txn := app.StartTransaction("hello", nil, nil) - connector, _ := dr.(driver.DriverContext).OpenConnector("myhost,myport,mydatabase") - conn, _ := connector.Connect(context.Background()) - ctx := NewContext(context.Background(), txn) - conn.(driver.ExecerContext).ExecContext(ctx, "myoperation,mycollection", nil) - txn.End() - app.ExpectMetrics(t, driverTestMetrics) -} - -func TestInstrumentSQLConnector(t *testing.T) { - // Test that connections returned by an instrumented driver.Connector - // are instrumented. - app := testApp(nil, nil, t) - bld := testBuilder - bld.BaseSegment.Host = "myhost" - bld.BaseSegment.PortPathOrID = "myport" - bld.BaseSegment.DatabaseName = "mydatabase" - connector := InstrumentSQLConnector(testConnector{}, bld) - txn := app.StartTransaction("hello", nil, nil) - conn, _ := connector.Connect(context.Background()) - ctx := NewContext(context.Background(), txn) - conn.(driver.ExecerContext).ExecContext(ctx, "myoperation,mycollection", nil) - txn.End() - app.ExpectMetrics(t, driverTestMetrics) -} - -func TestConnectorToDriver(t *testing.T) { - // Test that driver.Connector.Driver returns an instrumented Driver. - app := testApp(nil, nil, t) - connector := InstrumentSQLConnector(testConnector{}, testBuilder) - txn := app.StartTransaction("hello", nil, nil) - dr := connector.Driver() - conn, _ := dr.Open("myhost,myport,mydatabase") - ctx := NewContext(context.Background(), txn) - conn.(driver.ExecerContext).ExecContext(ctx, "myoperation,mycollection", nil) - txn.End() - app.ExpectMetrics(t, driverTestMetrics) -} diff --git a/transaction.go b/transaction.go deleted file mode 100644 index 63e8d9916..000000000 --- a/transaction.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "net/http" - "net/url" -) - -// Transaction instruments one logical unit of work: either an inbound web -// request or background task. Start a new Transaction with the -// Application.StartTransaction() method. -type Transaction interface { - // The transaction's http.ResponseWriter methods delegate to the - // http.ResponseWriter provided as a parameter to - // Application.StartTransaction or Transaction.SetWebResponse. This - // allows instrumentation of the response code and response headers. - // These methods may be called safely if the transaction does not have a - // http.ResponseWriter. - http.ResponseWriter - - // End finishes the Transaction. After that, subsequent calls to End or - // other Transaction methods have no effect. All segments and - // instrumentation must be completed before End is called. - End() error - - // Ignore prevents this transaction's data from being recorded. - Ignore() error - - // SetName names the transaction. Use a limited set of unique names to - // ensure that Transactions are grouped usefully. - SetName(name string) error - - // NoticeError records an error. The Transaction saves the first five - // errors. For more control over the recorded error fields, see the - // newrelic.Error type. In certain situations, using this method may - // result in an error being recorded twice: Errors are automatically - // recorded when Transaction.WriteHeader receives a status code above - // 400 or below 100 that is not in the IgnoreStatusCodes configuration - // list. This method is unaffected by the IgnoreStatusCodes - // configuration list. - NoticeError(err error) error - - // AddAttribute adds a key value pair to the transaction event, errors, - // and traces. - // - // The key must contain fewer than than 255 bytes. The value must be a - // number, string, or boolean. - // - // For more information, see: - // https://docs.newrelic.com/docs/agents/manage-apm-agents/agent-metrics/collect-custom-attributes - AddAttribute(key string, value interface{}) error - - // SetWebRequest marks the transaction as a web transaction. If - // WebRequest is non-nil, SetWebRequest will additionally collect - // details on request attributes, url, and method. If headers are - // present, the agent will look for a distributed tracing header. Use - // NewWebRequest to transform a *http.Request into a WebRequest. - SetWebRequest(WebRequest) error - - // SetWebResponse sets transaction's http.ResponseWriter. After calling - // this method, the transaction may be used in place of the - // ResponseWriter to intercept the response code. This method is useful - // when the ResponseWriter is not available at the beginning of the - // transaction (if so, it can be given as a parameter to - // Application.StartTransaction). This method will return a reference - // to the transaction which implements the combination of - // http.CloseNotifier, http.Flusher, http.Hijacker, and io.ReaderFrom - // implemented by the ResponseWriter. - SetWebResponse(http.ResponseWriter) Transaction - - // StartSegmentNow starts timing a segment. The SegmentStartTime - // returned can be used as the StartTime field in Segment, - // DatastoreSegment, or ExternalSegment. We recommend using the - // StartSegmentNow function instead of this method since it checks if - // the Transaction is nil. - StartSegmentNow() SegmentStartTime - - // CreateDistributedTracePayload creates a payload used to link - // transactions. CreateDistributedTracePayload should be called every - // time an outbound call is made since the payload contains a timestamp. - // - // StartExternalSegment calls CreateDistributedTracePayload, so you - // don't need to use it for outbound HTTP calls: Just use - // StartExternalSegment! - // - // This method never returns nil. If the application is disabled or not - // yet connected then this method returns a shim implementation whose - // methods return empty strings. - CreateDistributedTracePayload() DistributedTracePayload - - // AcceptDistributedTracePayload links transactions by accepting a - // distributed trace payload from another transaction. - // - // Application.StartTransaction calls this method automatically if a - // payload is present in the request headers. Therefore, this method - // does not need to be used for typical HTTP transactions. - // - // AcceptDistributedTracePayload should be used as early in the - // transaction as possible. It may not be called after a call to - // CreateDistributedTracePayload. - // - // The payload parameter may be a DistributedTracePayload, a string, or - // a []byte. - AcceptDistributedTracePayload(t TransportType, payload interface{}) error - - // Application returns the Application which started the transaction. - Application() Application - - // BrowserTimingHeader generates the JavaScript required to enable New - // Relic's Browser product. This code should be placed into your pages - // as close to the top of the element as possible, but after any - // position-sensitive tags (for example, X-UA-Compatible or - // charset information). - // - // This function freezes the transaction name: any calls to SetName() - // after BrowserTimingHeader() will be ignored. - // - // The *BrowserTimingHeader return value will be nil if browser - // monitoring is disabled, the application is not connected, or an error - // occurred. It is safe to call the pointer's methods if it is nil. - BrowserTimingHeader() (*BrowserTimingHeader, error) - - // NewGoroutine allows you to use the Transaction in multiple - // goroutines. - // - // Each goroutine must have its own Transaction reference returned by - // NewGoroutine. You must call NewGoroutine to get a new Transaction - // reference every time you wish to pass the Transaction to another - // goroutine. It does not matter if you call this before or after the - // other goroutine has started. - // - // All Transaction methods can be used in any Transaction reference. - // The Transaction will end when End() is called in any goroutine. - // - // Example passing a new Transaction reference directly to another - // goroutine: - // - // go func(txn newrelic.Transaction) { - // defer newrelic.StartSegment(txn, "async").End() - // time.Sleep(100 * time.Millisecond) - // }(txn.NewGoroutine()) - // - // Example passing a new Transaction reference on a channel to another - // goroutine: - // - // ch := make(chan newrelic.Transaction) - // go func() { - // txn := <-ch - // defer newrelic.StartSegment(txn, "async").End() - // time.Sleep(100 * time.Millisecond) - // }() - // ch <- txn.NewGoroutine() - // - NewGoroutine() Transaction - - // GetTraceMetadata returns distributed tracing identifiers. Empty - // string identifiers are returned if the transaction has finished. - GetTraceMetadata() TraceMetadata - - // GetLinkingMetadata returns the fields needed to link data to a trace or - // entity. - GetLinkingMetadata() LinkingMetadata - - // IsSampled indicates if the Transaction is sampled. A sampled - // Transaction records a span event for each segment. Distributed tracing - // must be enabled for transactions to be sampled. False is returned if - // the transaction has finished. - IsSampled() bool -} - -// DistributedTracePayload traces requests between applications or processes. -// DistributedTracePayloads are automatically added to HTTP requests by -// StartExternalSegment, so you only need to use this if you are tracing through -// a message queue or another non-HTTP communication library. The -// DistributedTracePayload may be marshalled in one of two formats: HTTPSafe or -// Text. All New Relic agents can accept payloads in either format. -type DistributedTracePayload interface { - // HTTPSafe serializes the payload into a string containing http safe - // characters. - HTTPSafe() string - // Text serializes the payload into a string. The format is slightly - // more compact than HTTPSafe. - Text() string -} - -const ( - // DistributedTracePayloadHeader is the header used by New Relic agents - // for automatic trace payload instrumentation. - DistributedTracePayloadHeader = "Newrelic" -) - -// TransportType is used in Transaction.AcceptDistributedTracePayload() to -// represent the type of connection that the trace payload was transported over. -type TransportType struct{ name string } - -// TransportType names used across New Relic agents: -var ( - TransportUnknown = TransportType{name: "Unknown"} - TransportHTTP = TransportType{name: "HTTP"} - TransportHTTPS = TransportType{name: "HTTPS"} - TransportKafka = TransportType{name: "Kafka"} - TransportJMS = TransportType{name: "JMS"} - TransportIronMQ = TransportType{name: "IronMQ"} - TransportAMQP = TransportType{name: "AMQP"} - TransportQueue = TransportType{name: "Queue"} - TransportOther = TransportType{name: "Other"} -) - -// WebRequest may be implemented to provide request information to -// Transaction.SetWebRequest. -type WebRequest interface { - // Header may return nil if you don't have any headers or don't want to - // transform them to http.Header format. - Header() http.Header - // URL may return nil if you don't have a URL or don't want to transform - // it to *url.URL. - URL() *url.URL - Method() string - // If a distributed tracing header is found in the headers returned by - // Header(), this TransportType will be used in the distributed tracing - // metrics. - Transport() TransportType -} - -// NewWebRequest turns a *http.Request into a WebRequest for input into -// Transaction.SetWebRequest. -func NewWebRequest(request *http.Request) WebRequest { - if nil == request { - return nil - } - return requestWrap{request: request} -} - -// NewStaticWebRequest takes the minimum necessary information and creates a static WebRequest out of it -func NewStaticWebRequest(hdrs http.Header, url *url.URL, method string, transport TransportType) WebRequest { - return staticWebRequest{hdrs, url, method, transport} -} - -// LinkingMetadata is returned by Transaction.GetLinkingMetadata. It contains -// identifiers needed link data to a trace or entity. -type LinkingMetadata struct { - // TraceID identifies the entire distributed trace. This field is empty - // if distributed tracing is disabled. - TraceID string - // SpanID identifies the currently active segment. This field is empty - // if distributed tracing is disabled or the transaction is not sampled. - SpanID string - // EntityName is the Application name as set on the newrelic.Config. If - // multiple application names are specified, only the first is returned. - EntityName string - // EntityType is the type of this entity and is always the string - // "SERVICE". - EntityType string - // EntityGUID is the unique identifier for this entity. - EntityGUID string - // Hostname is the hostname this entity is running on. - Hostname string -} - -// TraceMetadata is returned by Transaction.GetTraceMetadata. It contains -// distributed tracing identifiers. -type TraceMetadata struct { - // TraceID identifies the entire distributed trace. This field is empty - // if distributed tracing is disabled. - TraceID string - // SpanID identifies the currently active segment. This field is empty - // if distributed tracing is disabled or the transaction is not sampled. - SpanID string -} diff --git a/v3/integrations/nrlogrus/go.mod b/v3/integrations/nrlogrus/go.mod index 2c2041181..d3ae7fb67 100644 --- a/v3/integrations/nrlogrus/go.mod +++ b/v3/integrations/nrlogrus/go.mod @@ -6,9 +6,10 @@ go 1.19 require ( github.com/newrelic/go-agent/v3 v3.26.0 + github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrlogrus v1.0.0 // v1.1.0 is required for the Logger.GetLevel method, and is the earliest // version of logrus using modules. - github.com/sirupsen/logrus v1.1.0 + github.com/sirupsen/logrus v1.8.1 ) diff --git a/v3/integrations/nrmysql/go.mod b/v3/integrations/nrmysql/go.mod index 54607bb30..65b0b2844 100644 --- a/v3/integrations/nrmysql/go.mod +++ b/v3/integrations/nrmysql/go.mod @@ -10,4 +10,5 @@ require ( github.com/newrelic/go-agent/v3 v3.26.0 ) + replace github.com/newrelic/go-agent/v3 => ../.. diff --git a/v3/integrations/nrstan/test/go.mod b/v3/integrations/nrstan/test/go.mod index 6002bc674..589f16ebf 100644 --- a/v3/integrations/nrstan/test/go.mod +++ b/v3/integrations/nrstan/test/go.mod @@ -5,12 +5,13 @@ module github.com/newrelic/go-agent/v3/integrations/nrstan/test go 1.19 require ( - github.com/nats-io/nats-streaming-server v0.24.3 + github.com/nats-io/nats-streaming-server v0.25.5 github.com/nats-io/stan.go v0.10.4 github.com/newrelic/go-agent/v3 v3.26.0 github.com/newrelic/go-agent/v3/integrations/nrstan v0.0.0 ) + replace github.com/newrelic/go-agent/v3/integrations/nrstan => ../ replace github.com/newrelic/go-agent/v3 => ../../.. diff --git a/version.go b/version.go deleted file mode 100644 index 15f922114..000000000 --- a/version.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020 New Relic Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package newrelic - -import ( - "runtime" - - "github.com/newrelic/go-agent/internal" -) - -const ( - major = "2" - minor = "16" - patch = "4" - - // Version is the full string version of this Go Agent. - Version = major + "." + minor + "." + patch -) - -func init() { - internal.TrackUsage("Go", "Version", Version) - internal.TrackUsage("Go", "Runtime", "Version", internal.MinorVersion(runtime.Version())) -} From 37c399b90c22319d75c574ed02ac2b04e57dbd9c Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Tue, 17 Oct 2023 13:50:37 -0400 Subject: [PATCH 03/19] fix nrmongo test and nrnats test --- .github/workflows/ci.yaml | 1 - v3/build-script.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2e3b02c6d..a97c4d5e4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -137,7 +137,6 @@ jobs: dirs: v3/integrations/nrb3 - go-version: 1.19.x dirs: v3/integrations/nrmongo - extratesting: go get -u go.mongodb.org/mongo-driver@master - go-version: 1.19.x dirs: v3/integrations/nrgraphqlgo,v3/integrations/nrgraphqlgo/example extratesting: go get -u github.com/graphql-go/graphql@master diff --git a/v3/build-script.sh b/v3/build-script.sh index ee85bb008..780c72611 100755 --- a/v3/build-script.sh +++ b/v3/build-script.sh @@ -44,7 +44,7 @@ for dir in $DIRS; do go mod edit -replace github.com/newrelic/go-agent/v3="$pwd"/v3 # manage dependencies - go mod tidy -go=$shortVersion -compat=$shortVersion + GOPROXY=direct go mod tidy -go=$shortVersion -compat=$shortVersion pin_go_dependency "$PIN" # run tests From 908226e1551abd3644823855495e5230988f4168 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Tue, 17 Oct 2023 14:01:52 -0400 Subject: [PATCH 04/19] replace old arm64 go link with current one --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a97c4d5e4..4e55aac23 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -200,7 +200,7 @@ jobs: install: | DEBIAN_FRONTEND=noninteractive apt-get -qq update DEBIAN_FRONTEND=noninteractive apt-get -qq install -y wget build-essential - wget -nv https://golang.org/dl/go${{ matrix.go-version }}.linux-arm64.tar.gz + wget -nv https://go.dev/dl/go${{ matrix.go-version }}.linux-arm64.tar.gz rm -rf /usr/local/go tar -C /usr/local -xzf go${{ matrix.go-version }}.linux-arm64.tar.gz run: | From 604dd638768555d4ce7c484a126b08913780fbb1 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Tue, 17 Oct 2023 14:17:54 -0400 Subject: [PATCH 05/19] target nats with goproxy direct --- .github/workflows/ci.yaml | 1 + v3/build-script.sh | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4e55aac23..ff67a490d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -117,6 +117,7 @@ jobs: - go-version: 1.19.x dirs: v3/integrations/nrnats extratesting: go get -u github.com/nats-io/nats.go/@master + goproxy: direct - go-version: 1.19.x dirs: v3/integrations/nrstan extratesting: go get -u github.com/nats-io/stan.go/@master diff --git a/v3/build-script.sh b/v3/build-script.sh index 780c72611..66eae3f9a 100755 --- a/v3/build-script.sh +++ b/v3/build-script.sh @@ -44,7 +44,12 @@ for dir in $DIRS; do go mod edit -replace github.com/newrelic/go-agent/v3="$pwd"/v3 # manage dependencies - GOPROXY=direct go mod tidy -go=$shortVersion -compat=$shortVersion + if [ $goproxy -eq "direct" ]; then + GOPROXY=direct go mod tidy -go=$shortVersion -compat=$shortVersion + else + go mod tidy -go=$shortVersion -compat=$shortVersion + fi + pin_go_dependency "$PIN" # run tests From d6cfec82df52424af68e7cb2b0ef5d0505511d3e Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Tue, 17 Oct 2023 14:28:41 -0400 Subject: [PATCH 06/19] bad bash --- v3/build-script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/build-script.sh b/v3/build-script.sh index 66eae3f9a..71ebe5c0b 100755 --- a/v3/build-script.sh +++ b/v3/build-script.sh @@ -44,7 +44,7 @@ for dir in $DIRS; do go mod edit -replace github.com/newrelic/go-agent/v3="$pwd"/v3 # manage dependencies - if [ $goproxy -eq "direct" ]; then + if [[ "$goproxy" -eq "direct" ]]; then GOPROXY=direct go mod tidy -go=$shortVersion -compat=$shortVersion else go mod tidy -go=$shortVersion -compat=$shortVersion From 6a63cdc84a8029e0da9b5fa51e1be397a6a27c3e Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Tue, 17 Oct 2023 14:36:34 -0400 Subject: [PATCH 07/19] bad bash part 2 --- v3/build-script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3/build-script.sh b/v3/build-script.sh index 71ebe5c0b..1ef8f8769 100755 --- a/v3/build-script.sh +++ b/v3/build-script.sh @@ -44,7 +44,7 @@ for dir in $DIRS; do go mod edit -replace github.com/newrelic/go-agent/v3="$pwd"/v3 # manage dependencies - if [[ "$goproxy" -eq "direct" ]]; then + if [ "$goproxy" = "direct" ]; then GOPROXY=direct go mod tidy -go=$shortVersion -compat=$shortVersion else go mod tidy -go=$shortVersion -compat=$shortVersion From a4a45c25374163c54539490c4da117ccbd4877bf Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Tue, 17 Oct 2023 14:57:22 -0400 Subject: [PATCH 08/19] final goproxy solution: --- .github/workflows/ci.yaml | 1 + v3/build-script.sh | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ff67a490d..fa5d5bdb4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -168,6 +168,7 @@ jobs: DIRS: ${{ matrix.dirs }} EXTRATESTING: ${{ matrix.extratesting }} PIN: ${{ matrix.pin }} + GOPROXY: ${{ matrix.goproxy }} - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/v3/build-script.sh b/v3/build-script.sh index 1ef8f8769..ee85bb008 100755 --- a/v3/build-script.sh +++ b/v3/build-script.sh @@ -44,12 +44,7 @@ for dir in $DIRS; do go mod edit -replace github.com/newrelic/go-agent/v3="$pwd"/v3 # manage dependencies - if [ "$goproxy" = "direct" ]; then - GOPROXY=direct go mod tidy -go=$shortVersion -compat=$shortVersion - else - go mod tidy -go=$shortVersion -compat=$shortVersion - fi - + go mod tidy -go=$shortVersion -compat=$shortVersion pin_go_dependency "$PIN" # run tests From fa926b94a68999f1c19ebdf9860e2ee7fc3429e1 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Tue, 17 Oct 2023 15:05:13 -0400 Subject: [PATCH 09/19] bug in nats extratesting --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fa5d5bdb4..e12881302 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -116,7 +116,7 @@ jobs: # If we are using the latest released version to test, we need to use a newer version of go - go-version: 1.19.x dirs: v3/integrations/nrnats - extratesting: go get -u github.com/nats-io/nats.go/@master + extratesting: go get -u github.com/nats-io/nats.go@master goproxy: direct - go-version: 1.19.x dirs: v3/integrations/nrstan From 0d2b9324ede992cbe227b281937054bb6f7f3512 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 09:25:37 -0500 Subject: [PATCH 10/19] Bump github.com/graphql-go/graphql from 0.7.9 to 0.8.1 in /v3/integrations/nrgraphqlgo/example (#786) * Bump github.com/graphql-go/graphql Bumps [github.com/graphql-go/graphql](https://github.com/graphql-go/graphql) from 0.7.9 to 0.8.1. - [Release notes](https://github.com/graphql-go/graphql/releases) - [Commits](https://github.com/graphql-go/graphql/compare/v0.7.9...v0.8.1) --- updated-dependencies: - dependency-name: github.com/graphql-go/graphql dependency-type: direct:production ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: Mirac Kara <55501260+mirackara@users.noreply.github.com> Co-authored-by: aayush-ap Co-authored-by: Steve Willoughby <76975199+nr-swilloughby@users.noreply.github.com> Co-authored-by: Julien Erard Co-authored-by: Emilio Garcia Co-authored-by: Steve Willoughby Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- v3/integrations/nrgraphqlgo/example/go.mod | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/v3/integrations/nrgraphqlgo/example/go.mod b/v3/integrations/nrgraphqlgo/example/go.mod index cb5bfcbfb..d5a044811 100644 --- a/v3/integrations/nrgraphqlgo/example/go.mod +++ b/v3/integrations/nrgraphqlgo/example/go.mod @@ -1,10 +1,29 @@ module github.com/newrelic/go-agent/v3/integrations/nrgraphqlgo/example + go 1.19 + require ( - github.com/graphql-go/graphql v0.7.9 + github.com/graphql-go/graphql v0.8.1 github.com/graphql-go/graphql-go-handler v0.2.3 github.com/newrelic/go-agent/v3 v3.26.0 github.com/newrelic/go-agent/v3/integrations/nrgraphqlgo v1.0.0 ) + +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/graphql-go/handler v0.2.3 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.49.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) + replace github.com/newrelic/go-agent/v3/integrations/nrgraphqlgo => ../ + replace github.com/newrelic/go-agent/v3 => ../../.. From fd9e02601d84a8a66fd93d6594c7fbe37361776a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 09:27:09 -0500 Subject: [PATCH 11/19] Bump github.com/graphql-go/graphql from 0.7.9 to 0.8.1 in /v3/integrations/nrgraphqlgo (#785) * Bump github.com/graphql-go/graphql in /v3/integrations/nrgraphqlgo Bumps [github.com/graphql-go/graphql](https://github.com/graphql-go/graphql) from 0.7.9 to 0.8.1. - [Release notes](https://github.com/graphql-go/graphql/releases) - [Commits](https://github.com/graphql-go/graphql/compare/v0.7.9...v0.8.1) --- updated-dependencies: - dependency-name: github.com/graphql-go/graphql dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- v3/integrations/nrgraphqlgo/go.mod | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/v3/integrations/nrgraphqlgo/go.mod b/v3/integrations/nrgraphqlgo/go.mod index e88d3ae77..50f53434f 100644 --- a/v3/integrations/nrgraphqlgo/go.mod +++ b/v3/integrations/nrgraphqlgo/go.mod @@ -3,9 +3,22 @@ module github.com/newrelic/go-agent/v3/integrations/nrgraphqlgo go 1.19 require ( - github.com/graphql-go/graphql v0.7.9 + github.com/graphql-go/graphql v0.8.1 github.com/newrelic/go-agent/v3 v3.26.0 ) +require ( + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/klauspost/compress v1.16.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.49.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/grpc v1.54.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) replace github.com/newrelic/go-agent/v3 => ../.. From e661752be3927d9c8b0c4a5b92f63cc3f869d8e0 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Wed, 18 Oct 2023 11:55:26 -0400 Subject: [PATCH 12/19] bump actions for setting up go --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e12881302..6bb178104 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -147,7 +147,7 @@ jobs: steps: - name: Install Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} From 12551d1c003fc4af9cdd86ff14492abc3e732d39 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Wed, 18 Oct 2023 12:53:21 -0400 Subject: [PATCH 13/19] Update go.mod clean up nramqp go.mod --- v3/integrations/nramqp/go.mod | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/v3/integrations/nramqp/go.mod b/v3/integrations/nramqp/go.mod index 0e8bf8c12..c099244b4 100644 --- a/v3/integrations/nramqp/go.mod +++ b/v3/integrations/nramqp/go.mod @@ -1,23 +1,8 @@ module github.com/newrelic/go-agent/v3/integrations/nramqp -go 1.19.0 +go 1.19 require ( github.com/newrelic/go-agent/v3 v3.27.0 github.com/rabbitmq/amqp091-go v1.9.0 ) - -require ( - github.com/andybalholm/brotli v1.0.6 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/klauspost/compress v1.17.1 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.50.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect -) From 5a5b53d7cea018dfecfb33c167e0d0d5ddbb284b Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Thu, 19 Oct 2023 10:50:44 -0400 Subject: [PATCH 14/19] Update ci.yaml upgrade nats to latest stable version, not master branch (doesn't exist) --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6bb178104..a7debd44e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -116,7 +116,7 @@ jobs: # If we are using the latest released version to test, we need to use a newer version of go - go-version: 1.19.x dirs: v3/integrations/nrnats - extratesting: go get -u github.com/nats-io/nats.go@master + extratesting: go get -u github.com/nats-io/nats.go goproxy: direct - go-version: 1.19.x dirs: v3/integrations/nrstan From f896274a52b7f716f93021756990ead2fe8369af Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Thu, 19 Oct 2023 10:56:00 -0400 Subject: [PATCH 15/19] cleanup for rabbitmq release --- v3/newrelic/transaction.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index 3f75945ed..d553f68b1 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -589,11 +589,6 @@ func (tt TransportType) toString() string { } } -const ( - RabbitMQMessageBrokerLibrary = "RabbitMQ" - ExchangeDestinationType = "Exchange" -) - // WebRequest is used to provide request information to Transaction.SetWebRequest. type WebRequest struct { // Header may be nil if you don't have any headers or don't want to From 2faf688fbea9220e148b60415440222896220076 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Thu, 19 Oct 2023 11:58:26 -0400 Subject: [PATCH 16/19] release 3.27.0 --- CHANGELOG.md | 15 +++++++++++++++ v3/newrelic/version.go | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9049b6af1..7c696edb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 3.27.0 +### Added + * Added Support for getting Container ID's from cgroup v2 docker containers + * A new instrumentation package for RabbitMQ with distributed tracing support: nramqp + +### Fixed + * Unit tests repairs and improvements + * Removed deprecated V2 code from the repository. The support timeframe for this code has expired and is no longer recommended for use. + * Bumped github.com/graphql-go/graphql from 0.7.9 to 0.8.1 + +### Support statement + We use the latest version of the Go language. At minimum, you should be using no version of Go older than what is supported by the Go team themselves. + + See the [Go agent EOL Policy](https://docs.newrelic.com/docs/apm/agents/go-agent/get-started/go-agent-eol-policy/) for details about supported versions of the Go agent and third-party components. + ## 3.26.0 ### Added * Extended implementation of the `nrpgx5` integration (now v1.2.0). This instruments Postgres database operations using the `jackc/pgx/v5` library, including the direct access mode of operation as opposed to requiring code to use the library compatibly with the standard `database/sql` library. diff --git a/v3/newrelic/version.go b/v3/newrelic/version.go index 17f1f1f68..0b8c0fc6f 100644 --- a/v3/newrelic/version.go +++ b/v3/newrelic/version.go @@ -11,7 +11,7 @@ import ( const ( // Version is the full string version of this Go Agent. - Version = "3.26.0" + Version = "3.27.0" ) var ( From 05ee293c930406ca2a4e6c3435b411a1efee7529 Mon Sep 17 00:00:00 2001 From: Mirac Kara <55501260+mirackara@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:18:58 -0500 Subject: [PATCH 17/19] Docker ContainerID Support for CGroupV2 (#797) * DockerID Support for MountInfo --- .../docker_container_id/cases.json | 5 +++ .../docker_container_id/mountinfo.txt | 21 +++++++++++ v3/internal/sysinfo/docker.go | 36 +++++++++++++++++-- v3/internal/sysinfo/docker_test.go | 6 +++- 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 v3/internal/crossagent/cross_agent_tests/docker_container_id/mountinfo.txt diff --git a/v3/internal/crossagent/cross_agent_tests/docker_container_id/cases.json b/v3/internal/crossagent/cross_agent_tests/docker_container_id/cases.json index f1f9c0e61..4f9717792 100644 --- a/v3/internal/crossagent/cross_agent_tests/docker_container_id/cases.json +++ b/v3/internal/crossagent/cross_agent_tests/docker_container_id/cases.json @@ -1,4 +1,9 @@ [ + { + "filename": "mountinfo.txt", + "containerId": "ec807d5258c06c355c07e2acb700f9029d820afe5836d6a7e19764773dc790f5", + "expectedMetrics": null + }, { "filename": "docker-0.9.1.txt", "containerId": "f37a7e4d17017e7bf774656b19ca4360c6cdc4951c86700a464101d0d9ce97ee", diff --git a/v3/internal/crossagent/cross_agent_tests/docker_container_id/mountinfo.txt b/v3/internal/crossagent/cross_agent_tests/docker_container_id/mountinfo.txt new file mode 100644 index 000000000..cb1f3850a --- /dev/null +++ b/v3/internal/crossagent/cross_agent_tests/docker_container_id/mountinfo.txt @@ -0,0 +1,21 @@ +787 677 0:205 / / rw,relatime master:211 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/4LWPEJQQP4ZTPKM5BERIGWX63N:/var/lib/docker/overlay2/l/TFA7XS7THOIUG2XOSPD63ZTVCW:/var/lib/docker/overlay2/l/LU2GER2CKIEZEN3O3ZM7MMN7FE:/var/lib/docker/overlay2/l/C56BBR5IUNPYU7VFQYBD7B6TRV:/var/lib/docker/overlay2/l/3H5SWJGWS5HFV3EVBSXW3Z5EWP:/var/lib/docker/overlay2/l/FZSKODSSYVKREFDR7EOHUS4C52:/var/lib/docker/overlay2/l/X4HBP5ZZCMRNDQROSJCS3FPJXN:/var/lib/docker/overlay2/l/YXPJDSIAVYL3AQXJOMJKC2UBQ7:/var/lib/docker/overlay2/l/S3H6KC6FPHLB4YMN24TNILEUIG:/var/lib/docker/overlay2/l/Y3UADAXTZUWRMXPRAFLWZEHC4O,upperdir=/var/lib/docker/overlay2/88a9371f4db27c41c3013cea0af17d1a51d8a591a659dafe0ee4f4f5aa07ea34/diff,workdir=/var/lib/docker/overlay2/88a9371f4db27c41c3013cea0af17d1a51d8a591a659dafe0ee4f4f5aa07ea34/work +788 787 0:208 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw +789 787 0:209 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +790 789 0:210 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666 +791 787 0:211 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro +792 791 0:33 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw +793 789 0:207 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw +794 789 0:212 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k +795 787 254:1 /docker/containers/ec807d5258c06c355c07e2acb700f9029d820afe5836d6a7e19764773dc790f5/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/vda1 rw +796 787 254:1 /docker/containers/ec807d5258c06c355c07e2acb700f9029d820afe5836d6a7e19764773dc790f5/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw +797 787 254:1 /docker/containers/ec807d5258c06c355c07e2acb700f9029d820afe5836d6a7e19764773dc790f5/hosts /etc/hosts rw,relatime - ext4 /dev/vda1 rw +678 788 0:208 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw +679 788 0:208 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw +680 788 0:208 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw +681 788 0:208 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw +682 788 0:208 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw +683 788 0:209 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +684 788 0:209 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +685 788 0:209 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +686 788 0:209 /null /proc/sched_debug rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755 +687 791 0:213 / /sys/firmware ro,relatime - tmpfs tmpfs ro \ No newline at end of file diff --git a/v3/internal/sysinfo/docker.go b/v3/internal/sysinfo/docker.go index 97deee896..d7499bb22 100644 --- a/v3/internal/sysinfo/docker.go +++ b/v3/internal/sysinfo/docker.go @@ -12,6 +12,7 @@ import ( "os" "regexp" "runtime" + "strings" ) var ( @@ -31,8 +32,18 @@ func DockerID() (string, error) { return "", err } defer f.Close() + id, err := parseDockerID(f) - return parseDockerID(f) + // Attempt mountinfo file lookup if DockerID not found in cgroup file + if err == ErrDockerNotFound { + f, err := os.Open("/proc/self/mountinfo") + if err != nil { + return "", err + } + defer f.Close() + return parseDockerIDMountInfo(f) + } + return id, err } var ( @@ -43,6 +54,27 @@ var ( dockerIDRegex = regexp.MustCompile(dockerIDRegexRaw) ) +func parseDockerIDMountInfo(r io.Reader) (string, error) { + // Each Line in the mountinfo file starts with a set of IDs before showing the path file we actually want + // 1. Mount ID + // 2. Parent ID + // 3. Major and minor device numbers + // 4. Path to ContainerID + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, "/docker/containers/") { + id := dockerIDRegex.FindString(line) + if err := validateDockerID(id); err != nil { + return "", err + } + return id, nil + } + } + return "", ErrDockerNotFound +} + func parseDockerID(r io.Reader) (string, error) { // Each line in the cgroup file consists of three colon delimited fields. // 1. hierarchy ID - we don't care about this @@ -51,7 +83,6 @@ func parseDockerID(r io.Reader) (string, error) { // // Example // 5:cpuacct,cpu,cpuset:/daemons - var id string for scanner := bufio.NewScanner(r); scanner.Scan(); { @@ -77,7 +108,6 @@ func parseDockerID(r io.Reader) (string, error) { } return id, nil } - return "", ErrDockerNotFound } diff --git a/v3/internal/sysinfo/docker_test.go b/v3/internal/sysinfo/docker_test.go index 0bfd20af3..485383e9a 100644 --- a/v3/internal/sysinfo/docker_test.go +++ b/v3/internal/sysinfo/docker_test.go @@ -33,7 +33,11 @@ func TestDockerIDCrossAgent(t *testing.T) { got, _ := parseDockerID(bytes.NewReader(input)) if got != test.ID { - t.Errorf("%s != %s", got, test.ID) + mountInfoAttempt, _ := parseDockerIDMountInfo(bytes.NewReader(input)) + if mountInfoAttempt != test.ID { + t.Errorf("MountInfo Attempt: %s != %s", mountInfoAttempt, test.ID) + t.Errorf("Traditional Attempt: %s != %s", got, test.ID) + } } } } From 1765c5a6ccf22a9fc444df1002fc8f55ca0dcac7 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Thu, 19 Oct 2023 14:25:26 -0400 Subject: [PATCH 18/19] cleanup for 3.27.0 release' --- v3/go.mod | 1 - v3/integrations/nrgraphqlgo/go.mod | 16 ---------------- v3/newrelic/transaction.go | 1 - 3 files changed, 18 deletions(-) diff --git a/v3/go.mod b/v3/go.mod index dbef3bf7d..02c244f6b 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -8,7 +8,6 @@ require ( google.golang.org/grpc v1.54.0 ) - retract v3.22.0 // release process error corrected in v3.22.1 retract v3.25.0 // release process error corrected in v3.25.1 diff --git a/v3/integrations/nrgraphqlgo/go.mod b/v3/integrations/nrgraphqlgo/go.mod index 50f53434f..e6eb1f0f6 100644 --- a/v3/integrations/nrgraphqlgo/go.mod +++ b/v3/integrations/nrgraphqlgo/go.mod @@ -6,19 +6,3 @@ require ( github.com/graphql-go/graphql v0.8.1 github.com/newrelic/go-agent/v3 v3.26.0 ) - -require ( - github.com/andybalholm/brotli v1.0.5 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/klauspost/compress v1.16.3 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.49.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect -) - -replace github.com/newrelic/go-agent/v3 => ../.. diff --git a/v3/newrelic/transaction.go b/v3/newrelic/transaction.go index d553f68b1..d1d519c63 100644 --- a/v3/newrelic/transaction.go +++ b/v3/newrelic/transaction.go @@ -352,7 +352,6 @@ func (txn *Transaction) InsertDistributedTraceHeaders(hdrs http.Header) { if txn == nil || txn.thread == nil { return } - txn.thread.CreateDistributedTracePayload(hdrs) } From 8ff7dc7cb5fc13eabc9adb6d8924ca605d1afa7d Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Thu, 19 Oct 2023 14:31:29 -0400 Subject: [PATCH 19/19] supportability metrics nramqp --- v3/integrations/nramqp/nramqp.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/v3/integrations/nramqp/nramqp.go b/v3/integrations/nramqp/nramqp.go index 7fd3ab258..2be8e7634 100644 --- a/v3/integrations/nramqp/nramqp.go +++ b/v3/integrations/nramqp/nramqp.go @@ -14,6 +14,8 @@ const ( RabbitMQLibrary = "RabbitMQ" ) +func init() { internal.TrackUsage("integration", "messagebroker", "nramqp") } + func creatProducerSegment(exchange, key string) *newrelic.MessageProducerSegment { s := newrelic.MessageProducerSegment{ Library: RabbitMQLibrary,