github.com/newrelic/go-agent@v3.26.0+incompatible/internal/connect_reply.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package internal
     5  
     6  import (
     7  	"encoding/json"
     8  	"strings"
     9  	"time"
    10  )
    11  
    12  // AgentRunID identifies the current connection with the collector.
    13  type AgentRunID string
    14  
    15  func (id AgentRunID) String() string {
    16  	return string(id)
    17  }
    18  
    19  // PreconnectReply contains settings from the preconnect endpoint.
    20  type PreconnectReply struct {
    21  	Collector        string           `json:"redirect_host"`
    22  	SecurityPolicies SecurityPolicies `json:"security_policies"`
    23  }
    24  
    25  // ConnectReply contains all of the settings and state send down from the
    26  // collector.  It should not be modified after creation.
    27  type ConnectReply struct {
    28  	RunID                 AgentRunID        `json:"agent_run_id"`
    29  	RequestHeadersMap     map[string]string `json:"request_headers_map"`
    30  	MaxPayloadSizeInBytes int               `json:"max_payload_size_in_bytes"`
    31  	EntityGUID            string            `json:"entity_guid"`
    32  
    33  	// Transaction Name Modifiers
    34  	SegmentTerms segmentRules `json:"transaction_segment_terms"`
    35  	TxnNameRules metricRules  `json:"transaction_name_rules"`
    36  	URLRules     metricRules  `json:"url_rules"`
    37  	MetricRules  metricRules  `json:"metric_name_rules"`
    38  
    39  	// Cross Process
    40  	EncodingKey     string            `json:"encoding_key"`
    41  	CrossProcessID  string            `json:"cross_process_id"`
    42  	TrustedAccounts trustedAccountSet `json:"trusted_account_ids"`
    43  
    44  	// Settings
    45  	KeyTxnApdex            map[string]float64 `json:"web_transactions_apdex"`
    46  	ApdexThresholdSeconds  float64            `json:"apdex_t"`
    47  	CollectAnalyticsEvents bool               `json:"collect_analytics_events"`
    48  	CollectCustomEvents    bool               `json:"collect_custom_events"`
    49  	CollectTraces          bool               `json:"collect_traces"`
    50  	CollectErrors          bool               `json:"collect_errors"`
    51  	CollectErrorEvents     bool               `json:"collect_error_events"`
    52  	CollectSpanEvents      bool               `json:"collect_span_events"`
    53  
    54  	// RUM
    55  	AgentLoader string `json:"js_agent_loader"`
    56  	Beacon      string `json:"beacon"`
    57  	BrowserKey  string `json:"browser_key"`
    58  	AppID       string `json:"application_id"`
    59  	ErrorBeacon string `json:"error_beacon"`
    60  	JSAgentFile string `json:"js_agent_file"`
    61  
    62  	// PreconnectReply fields are not in the connect reply, this embedding
    63  	// is done to simplify code.
    64  	PreconnectReply `json:"-"`
    65  
    66  	Messages []struct {
    67  		Message string `json:"message"`
    68  		Level   string `json:"level"`
    69  	} `json:"messages"`
    70  
    71  	AdaptiveSampler AdaptiveSampler
    72  	// TraceIDGenerator creates random IDs for distributed tracing.  It
    73  	// exists here in the connect reply so it can be modified to create
    74  	// deterministic identifiers in tests.
    75  	TraceIDGenerator *TraceIDGenerator `json:"-"`
    76  
    77  	// BetterCAT/Distributed Tracing
    78  	AccountID                     string `json:"account_id"`
    79  	TrustedAccountKey             string `json:"trusted_account_key"`
    80  	PrimaryAppID                  string `json:"primary_application_id"`
    81  	SamplingTarget                uint64 `json:"sampling_target"`
    82  	SamplingTargetPeriodInSeconds int    `json:"sampling_target_period_in_seconds"`
    83  
    84  	// rulesCache caches the results of calling CreateFullTxnName.  It
    85  	// exists here in ConnectReply since it is specific to a set of rules
    86  	// and is shared between transactions.
    87  	rulesCache *rulesCache
    88  
    89  	ServerSideConfig struct {
    90  		TransactionTracerEnabled *bool `json:"transaction_tracer.enabled"`
    91  		// TransactionTracerThreshold should contain either a number or
    92  		// "apdex_f" if it is non-nil.
    93  		TransactionTracerThreshold           interface{} `json:"transaction_tracer.transaction_threshold"`
    94  		TransactionTracerStackTraceThreshold *float64    `json:"transaction_tracer.stack_trace_threshold"`
    95  		ErrorCollectorEnabled                *bool       `json:"error_collector.enabled"`
    96  		ErrorCollectorIgnoreStatusCodes      []int       `json:"error_collector.ignore_status_codes"`
    97  		CrossApplicationTracerEnabled        *bool       `json:"cross_application_tracer.enabled"`
    98  	} `json:"agent_config"`
    99  
   100  	// Faster Event Harvest
   101  	EventData EventHarvestConfig `json:"event_harvest_config"`
   102  }
   103  
   104  // EventHarvestConfig contains fields relating to faster event harvest.
   105  // This structure is used in the connect request (to send up defaults)
   106  // and in the connect response (to get the server values).
   107  //
   108  // https://source.datanerd.us/agents/agent-specs/blob/master/Connect-LEGACY.md#event_harvest_config-hash
   109  // https://source.datanerd.us/agents/agent-specs/blob/master/Connect-LEGACY.md#event-harvest-config
   110  type EventHarvestConfig struct {
   111  	ReportPeriodMs int `json:"report_period_ms,omitempty"`
   112  	Limits         struct {
   113  		TxnEvents    *uint `json:"analytic_event_data,omitempty"`
   114  		CustomEvents *uint `json:"custom_event_data,omitempty"`
   115  		ErrorEvents  *uint `json:"error_event_data,omitempty"`
   116  		SpanEvents   *uint `json:"span_event_data,omitempty"`
   117  	} `json:"harvest_limits"`
   118  }
   119  
   120  // ConfigurablePeriod returns the Faster Event Harvest configurable reporting period if it is set, or the default
   121  // report period otherwise.
   122  func (r *ConnectReply) ConfigurablePeriod() time.Duration {
   123  	ms := DefaultConfigurableEventHarvestMs
   124  	if nil != r && r.EventData.ReportPeriodMs > 0 {
   125  		ms = r.EventData.ReportPeriodMs
   126  	}
   127  	return time.Duration(ms) * time.Millisecond
   128  }
   129  
   130  func uintPtr(x uint) *uint { return &x }
   131  
   132  // DefaultEventHarvestConfig provides faster event harvest defaults.
   133  func DefaultEventHarvestConfig(eventer MaxTxnEventer) EventHarvestConfig {
   134  	cfg := EventHarvestConfig{}
   135  	cfg.ReportPeriodMs = DefaultConfigurableEventHarvestMs
   136  	cfg.Limits.TxnEvents = uintPtr(uint(eventer.MaxTxnEvents()))
   137  	cfg.Limits.CustomEvents = uintPtr(uint(MaxCustomEvents))
   138  	cfg.Limits.ErrorEvents = uintPtr(uint(MaxErrorEvents))
   139  	return cfg
   140  }
   141  
   142  type trustedAccountSet map[int]struct{}
   143  
   144  func (t *trustedAccountSet) IsTrusted(account int) bool {
   145  	_, exists := (*t)[account]
   146  	return exists
   147  }
   148  
   149  func (t *trustedAccountSet) UnmarshalJSON(data []byte) error {
   150  	accounts := make([]int, 0)
   151  	if err := json.Unmarshal(data, &accounts); err != nil {
   152  		return err
   153  	}
   154  
   155  	*t = make(trustedAccountSet)
   156  	for _, account := range accounts {
   157  		(*t)[account] = struct{}{}
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  // ConnectReplyDefaults returns a newly allocated ConnectReply with the proper
   164  // default settings.  A pointer to a global is not used to prevent consumers
   165  // from changing the default settings.
   166  func ConnectReplyDefaults() *ConnectReply {
   167  	return &ConnectReply{
   168  		ApdexThresholdSeconds:  0.5,
   169  		CollectAnalyticsEvents: true,
   170  		CollectCustomEvents:    true,
   171  		CollectTraces:          true,
   172  		CollectErrors:          true,
   173  		CollectErrorEvents:     true,
   174  		CollectSpanEvents:      true,
   175  		MaxPayloadSizeInBytes:  maxPayloadSizeInBytes,
   176  		// No transactions should be sampled before the application is
   177  		// connected.
   178  		AdaptiveSampler: SampleNothing{},
   179  
   180  		SamplingTarget:                10,
   181  		SamplingTargetPeriodInSeconds: 60,
   182  
   183  		TraceIDGenerator: NewTraceIDGenerator(int64(time.Now().UnixNano())),
   184  	}
   185  }
   186  
   187  // CalculateApdexThreshold calculates the apdex threshold.
   188  func CalculateApdexThreshold(c *ConnectReply, txnName string) time.Duration {
   189  	if t, ok := c.KeyTxnApdex[txnName]; ok {
   190  		return FloatSecondsToDuration(t)
   191  	}
   192  	return FloatSecondsToDuration(c.ApdexThresholdSeconds)
   193  }
   194  
   195  // CreateFullTxnName uses collector rules and the appropriate metric prefix to
   196  // construct the full transaction metric name from the name given by the
   197  // consumer.
   198  func CreateFullTxnName(input string, reply *ConnectReply, isWeb bool) string {
   199  	if name := reply.rulesCache.find(input, isWeb); "" != name {
   200  		return name
   201  	}
   202  	name := constructFullTxnName(input, reply, isWeb)
   203  	if "" != name {
   204  		// Note that we  don't cache situations where the rules say
   205  		// ignore.  It would increase complication (we would need to
   206  		// disambiguate not-found vs ignore).  Also, the ignore code
   207  		// path is probably extremely uncommon.
   208  		reply.rulesCache.set(input, isWeb, name)
   209  	}
   210  	return name
   211  }
   212  
   213  func constructFullTxnName(input string, reply *ConnectReply, isWeb bool) string {
   214  	var afterURLRules string
   215  	if "" != input {
   216  		afterURLRules = reply.URLRules.Apply(input)
   217  		if "" == afterURLRules {
   218  			return ""
   219  		}
   220  	}
   221  
   222  	prefix := backgroundMetricPrefix
   223  	if isWeb {
   224  		prefix = webMetricPrefix
   225  	}
   226  
   227  	var beforeNameRules string
   228  	if strings.HasPrefix(afterURLRules, "/") {
   229  		beforeNameRules = prefix + afterURLRules
   230  	} else {
   231  		beforeNameRules = prefix + "/" + afterURLRules
   232  	}
   233  
   234  	afterNameRules := reply.TxnNameRules.Apply(beforeNameRules)
   235  	if "" == afterNameRules {
   236  		return ""
   237  	}
   238  
   239  	return reply.SegmentTerms.apply(afterNameRules)
   240  }