github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/agent.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2016
     3  
     4  package instana
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"net/http"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/instana/go-sensor/acceptor"
    19  	"github.com/instana/go-sensor/autoprofile"
    20  )
    21  
    22  var payloadTooLargeErr = errors.New(`request payload is too large`)
    23  
    24  const (
    25  	agentDiscoveryURL = "/com.instana.plugin.golang.discovery"
    26  	agentTracesURL    = "/com.instana.plugin.golang/traces."
    27  	agentDataURL      = "/com.instana.plugin.golang."
    28  	agentEventURL     = "/com.instana.plugin.generic.event"
    29  	agentProfilesURL  = "/com.instana.plugin.golang/profiles."
    30  	agentDefaultHost  = "localhost"
    31  	agentDefaultPort  = 42699
    32  	agentHeader       = "Instana Agent"
    33  
    34  	// SnapshotPeriod is the amount of time in seconds between snapshot reports.
    35  	SnapshotPeriod             = 600
    36  	snapshotCollectionInterval = SnapshotPeriod * time.Second
    37  
    38  	announceTimeout = 15 * time.Second
    39  	clientTimeout   = 5 * time.Second
    40  
    41  	maxContentLength      = 1024 * 1024 * 5
    42  	numberOfBigSpansToLog = 5
    43  )
    44  
    45  type agentResponse struct {
    46  	Pid     uint32 `json:"pid"`
    47  	HostID  string `json:"agentUuid"`
    48  	Secrets struct {
    49  		Matcher string   `json:"matcher"`
    50  		List    []string `json:"list"`
    51  	} `json:"secrets"`
    52  	ExtraHTTPHeaders []string `json:"extraHeaders"`
    53  	Tracing          struct {
    54  		ExtraHTTPHeaders []string `json:"extra-http-headers"`
    55  	} `json:"tracing"`
    56  }
    57  
    58  func (a *agentResponse) getExtraHTTPHeaders() []string {
    59  	if len(a.Tracing.ExtraHTTPHeaders) == 0 {
    60  		return a.ExtraHTTPHeaders
    61  	}
    62  
    63  	return a.Tracing.ExtraHTTPHeaders
    64  }
    65  
    66  type discoveryS struct {
    67  	PID               int      `json:"pid"`
    68  	Name              string   `json:"name"`
    69  	Args              []string `json:"args"`
    70  	Fd                string   `json:"fd"`
    71  	Inode             string   `json:"inode"`
    72  	CPUSetFileContent string   `json:"cpuSetFileContent"`
    73  }
    74  
    75  type fromS struct {
    76  	EntityID string `json:"e"`
    77  	// Serverless agents fields
    78  	Hostless      bool   `json:"hl,omitempty"`
    79  	CloudProvider string `json:"cp,omitempty"`
    80  	// Host agent fields
    81  	HostID string `json:"h,omitempty"`
    82  }
    83  
    84  func newServerlessAgentFromS(entityID, provider string) *fromS {
    85  	return &fromS{
    86  		EntityID:      entityID,
    87  		Hostless:      true,
    88  		CloudProvider: provider,
    89  	}
    90  }
    91  
    92  type httpClient interface {
    93  	Do(req *http.Request) (*http.Response, error)
    94  }
    95  
    96  type agentS struct {
    97  	// agentComm encapsulates info about the agent host and fromS. This is a shared information between the agent and
    98  	// the fsm layer, so we use this wrapper to prevent passing data from one side to the other in a more sophisticated
    99  	// way.
   100  	agentComm *agentCommunicator
   101  	port      string
   102  
   103  	mu  sync.RWMutex
   104  	fsm *fsmS
   105  
   106  	snapshot *SnapshotCollector
   107  	logger   LeveledLogger
   108  
   109  	printPayloadTooLargeErrInfoOnce sync.Once
   110  }
   111  
   112  func newAgent(serviceName, host string, port int, logger LeveledLogger) *agentS {
   113  	if logger == nil {
   114  		logger = defaultLogger
   115  	}
   116  
   117  	logger.Debug("initializing agent")
   118  
   119  	agent := &agentS{
   120  		agentComm: newAgentCommunicator(host, strconv.Itoa(port), &fromS{}, logger),
   121  		port:      strconv.Itoa(port),
   122  		snapshot: &SnapshotCollector{
   123  			CollectionInterval: snapshotCollectionInterval,
   124  			ServiceName:        serviceName,
   125  		},
   126  		logger: logger,
   127  	}
   128  
   129  	agent.mu.Lock()
   130  	agent.fsm = newFSM(agent.agentComm, logger)
   131  	agent.mu.Unlock()
   132  
   133  	return agent
   134  }
   135  
   136  // Ready returns whether the agent has finished the announcement and is ready to send data
   137  func (agent *agentS) Ready() bool {
   138  	agent.mu.RLock()
   139  	defer agent.mu.RUnlock()
   140  
   141  	return agent.fsm.fsm.Current() == "ready"
   142  }
   143  
   144  // SendMetrics sends collected entity data to the host agent
   145  func (agent *agentS) SendMetrics(data acceptor.Metrics) error {
   146  	pid, err := strconv.Atoi(agent.agentComm.from.EntityID)
   147  	if err != nil && agent.agentComm.from.EntityID != "" {
   148  		agent.logger.Debug("agent got malformed PID %q", agent.agentComm.from.EntityID)
   149  	}
   150  
   151  	if err := agent.agentComm.sendDataToAgent(agentDataURL, acceptor.GoProcessData{
   152  		PID:      pid,
   153  		Snapshot: agent.snapshot.Collect(),
   154  		Metrics:  data,
   155  	}); err != nil {
   156  		if err == payloadTooLargeErr {
   157  			agent.logger.Warn(`A batch of spans has been rejected because it is too large to be sent to the agent.`)
   158  		}
   159  
   160  		agent.logger.Error("failed to send metrics to the host agent: ", err)
   161  		agent.reset()
   162  
   163  		return err
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  // SendEvent sends an event using Instana Events API
   170  func (agent *agentS) SendEvent(event *EventData) error {
   171  	err := agent.agentComm.sendDataToAgent(agentEventURL, event)
   172  	if err != nil {
   173  		if err == payloadTooLargeErr {
   174  			agent.logger.Warn(`A batch of spans has been rejected because it is too large to be sent to the agent.`)
   175  		}
   176  
   177  		// do not reset the agent as it might be not initialized at this state yet
   178  		agent.logger.Warn("failed to send event ", event.Title, " to the host agent: ", err)
   179  
   180  		return err
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  // SendSpans sends collected spans to the host agent
   187  func (agent *agentS) SendSpans(spans []Span) error {
   188  	for i := range spans {
   189  		spans[i].From = agent.agentComm.from
   190  	}
   191  
   192  	err := agent.agentComm.sendDataToAgent(agentTracesURL, spans)
   193  	if err != nil {
   194  		if err == payloadTooLargeErr {
   195  			agent.printPayloadTooLargeErrInfoOnce.Do(
   196  				func() {
   197  					agent.logDetailedInformationAboutDroppedSpans(numberOfBigSpansToLog, spans, err)
   198  				},
   199  			)
   200  
   201  			return nil
   202  		} else {
   203  			agent.logger.Error("failed to send spans to the host agent: ", err)
   204  			agent.reset()
   205  		}
   206  
   207  		return err
   208  	}
   209  
   210  	return nil
   211  }
   212  
   213  // Flush is a noop for host agent
   214  func (agent *agentS) Flush(ctx context.Context) error { return nil }
   215  
   216  type hostAgentProfile struct {
   217  	autoprofile.Profile
   218  	ProcessID string `json:"pid"`
   219  }
   220  
   221  // SendProfiles sends profile data to the agent
   222  func (agent *agentS) SendProfiles(profiles []autoprofile.Profile) error {
   223  	agentProfiles := make([]hostAgentProfile, 0, len(profiles))
   224  	for _, p := range profiles {
   225  		agentProfiles = append(agentProfiles, hostAgentProfile{p, agent.agentComm.from.EntityID})
   226  	}
   227  
   228  	err := agent.agentComm.sendDataToAgent(agentProfilesURL, agentProfiles)
   229  	if err != nil {
   230  		if err == payloadTooLargeErr {
   231  			agent.logger.Warn(`A batch of spans has been rejected because it is too large to be sent to the agent.`)
   232  		}
   233  
   234  		agent.logger.Error("failed to send profile data to the host agent: ", err)
   235  		agent.reset()
   236  
   237  		return err
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  func (agent *agentS) setLogger(l LeveledLogger) {
   244  	agent.logger = l
   245  }
   246  
   247  func (agent *agentS) reset() {
   248  	agent.mu.Lock()
   249  	agent.fsm.reset()
   250  	agent.mu.Unlock()
   251  }
   252  
   253  func (agent *agentS) logDetailedInformationAboutDroppedSpans(size int, spans []Span, err error) {
   254  	var marshaledSpans []string
   255  	for i := range spans {
   256  		ms, err := json.Marshal(spans[i])
   257  		if err == nil {
   258  			marshaledSpans = append(marshaledSpans, string(ms))
   259  		}
   260  	}
   261  	sort.Slice(marshaledSpans, func(i, j int) bool {
   262  		// descending order
   263  		return len(marshaledSpans[i]) > len(marshaledSpans[j])
   264  	})
   265  
   266  	if size > len(marshaledSpans) {
   267  		size = len(marshaledSpans)
   268  	}
   269  
   270  	agent.logger.Warn(
   271  		fmt.Sprintf("failed to send spans to the host agent: dropped %d span(s) : %s.\nThis detailed information will only be logged once.\nSpans :\n %s",
   272  			len(spans),
   273  			err.Error(),
   274  			strings.Join(marshaledSpans[:size], ";"),
   275  		),
   276  	)
   277  }