github.com/mier85/go-sensor@v1.30.1-0.20220920111756-9bf41b3bc7e0/agent.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2016
     3  
     4  package instana
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"net/http"
    15  	"sort"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"time"
    20  
    21  	"github.com/mier85/go-sensor/acceptor"
    22  	"github.com/mier85/go-sensor/autoprofile"
    23  )
    24  
    25  var payloadTooLargeErr = errors.New(`request payload is too large`)
    26  
    27  const (
    28  	agentDiscoveryURL = "/com.instana.plugin.golang.discovery"
    29  	agentTracesURL    = "/com.instana.plugin.golang/traces."
    30  	agentDataURL      = "/com.instana.plugin.golang."
    31  	agentEventURL     = "/com.instana.plugin.generic.event"
    32  	agentProfilesURL  = "/com.instana.plugin.golang/profiles."
    33  	agentDefaultHost  = "localhost"
    34  	agentDefaultPort  = 42699
    35  	agentHeader       = "Instana Agent"
    36  
    37  	// SnapshotPeriod is the amount of time in seconds between snapshot reports.
    38  	SnapshotPeriod             = 600
    39  	snapshotCollectionInterval = SnapshotPeriod * time.Second
    40  
    41  	announceTimeout = 15 * time.Second
    42  	clientTimeout   = 5 * time.Second
    43  
    44  	maxContentLength      = 1024 * 1024 * 5
    45  	numberOfBigSpansToLog = 5
    46  )
    47  
    48  type agentResponse struct {
    49  	Pid     uint32 `json:"pid"`
    50  	HostID  string `json:"agentUuid"`
    51  	Secrets struct {
    52  		Matcher string   `json:"matcher"`
    53  		List    []string `json:"list"`
    54  	} `json:"secrets"`
    55  	ExtraHTTPHeaders []string `json:"extraHeaders"`
    56  	Tracing          struct {
    57  		ExtraHTTPHeaders []string `json:"extra-http-headers"`
    58  	} `json:"tracing"`
    59  }
    60  
    61  func (a *agentResponse) getExtraHTTPHeaders() []string {
    62  	if len(a.Tracing.ExtraHTTPHeaders) == 0 {
    63  		return a.ExtraHTTPHeaders
    64  	}
    65  
    66  	return a.Tracing.ExtraHTTPHeaders
    67  }
    68  
    69  type discoveryS struct {
    70  	PID               int      `json:"pid"`
    71  	Name              string   `json:"name"`
    72  	Args              []string `json:"args"`
    73  	Fd                string   `json:"fd"`
    74  	Inode             string   `json:"inode"`
    75  	CPUSetFileContent string   `json:"cpuSetFileContent"`
    76  }
    77  
    78  type fromS struct {
    79  	EntityID string `json:"e"`
    80  	// Serverless agents fields
    81  	Hostless      bool   `json:"hl,omitempty"`
    82  	CloudProvider string `json:"cp,omitempty"`
    83  	// Host agent fields
    84  	HostID string `json:"h,omitempty"`
    85  }
    86  
    87  func newHostAgentFromS(pid int, hostID string) *fromS {
    88  	return &fromS{
    89  		EntityID: strconv.Itoa(pid),
    90  		HostID:   hostID,
    91  	}
    92  }
    93  
    94  func newServerlessAgentFromS(entityID, provider string) *fromS {
    95  	return &fromS{
    96  		EntityID:      entityID,
    97  		Hostless:      true,
    98  		CloudProvider: provider,
    99  	}
   100  }
   101  
   102  type httpClient interface {
   103  	Do(req *http.Request) (*http.Response, error)
   104  }
   105  
   106  type agentS struct {
   107  	from *fromS
   108  	host string
   109  	port string
   110  
   111  	mu  sync.RWMutex
   112  	fsm *fsmS
   113  
   114  	client          httpClient
   115  	snapshot        *SnapshotCollector
   116  	logger          LeveledLogger
   117  	clientTimeout   time.Duration
   118  	announceTimeout time.Duration
   119  
   120  	printPayloadTooLargeErrInfoOnce sync.Once
   121  }
   122  
   123  func newAgent(serviceName, host string, port int, logger LeveledLogger) *agentS {
   124  	if logger == nil {
   125  		logger = defaultLogger
   126  	}
   127  
   128  	logger.Debug("initializing agent")
   129  
   130  	agent := &agentS{
   131  		from:            &fromS{},
   132  		host:            host,
   133  		port:            strconv.Itoa(port),
   134  		clientTimeout:   clientTimeout,
   135  		announceTimeout: announceTimeout,
   136  		client:          &http.Client{Timeout: announceTimeout},
   137  		snapshot: &SnapshotCollector{
   138  			CollectionInterval: snapshotCollectionInterval,
   139  			ServiceName:        serviceName,
   140  		},
   141  		logger: logger,
   142  	}
   143  
   144  	agent.mu.Lock()
   145  	agent.fsm = newFSM(agent, logger)
   146  	agent.mu.Unlock()
   147  
   148  	return agent
   149  }
   150  
   151  // Ready returns whether the agent has finished the announcement and is ready to send data
   152  func (agent *agentS) Ready() bool {
   153  	agent.mu.RLock()
   154  	defer agent.mu.RUnlock()
   155  
   156  	return agent.fsm.fsm.Current() == "ready"
   157  }
   158  
   159  // SendMetrics sends collected entity data to the host agent
   160  func (agent *agentS) SendMetrics(data acceptor.Metrics) error {
   161  	pid, err := strconv.Atoi(agent.from.EntityID)
   162  	if err != nil && agent.from.EntityID != "" {
   163  		agent.logger.Debug("agent got malformed PID %q", agent.from.EntityID)
   164  	}
   165  
   166  	if _, err = agent.request(agent.makeURL(agentDataURL), "POST", acceptor.GoProcessData{
   167  		PID:      pid,
   168  		Snapshot: agent.snapshot.Collect(),
   169  		Metrics:  data,
   170  	}); err != nil {
   171  		agent.logger.Error("failed to send metrics to the host agent: ", err)
   172  		agent.reset()
   173  
   174  		return err
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  // SendEvent sends an event using Instana Events API
   181  func (agent *agentS) SendEvent(event *EventData) error {
   182  	_, err := agent.request(agent.makeURL(agentEventURL), "POST", event)
   183  	if err != nil {
   184  		// do not reset the agent as it might be not initialized at this state yet
   185  		agent.logger.Warn("failed to send event ", event.Title, " to the host agent: ", err)
   186  
   187  		return err
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  // SendSpans sends collected spans to the host agent
   194  func (agent *agentS) SendSpans(spans []Span) error {
   195  	for i := range spans {
   196  		spans[i].From = agent.from
   197  	}
   198  
   199  	_, err := agent.request(agent.makeURL(agentTracesURL), "POST", spans)
   200  	if err != nil {
   201  		if err == payloadTooLargeErr {
   202  			agent.printPayloadTooLargeErrInfoOnce.Do(
   203  				func() {
   204  					agent.logDetailedInformationAboutDroppedSpans(numberOfBigSpansToLog, spans, err)
   205  				},
   206  			)
   207  
   208  			return nil
   209  		} else {
   210  			agent.logger.Error("failed to send spans to the host agent: ", err)
   211  			agent.reset()
   212  		}
   213  
   214  		return err
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  // Flush is a noop for host agent
   221  func (agent *agentS) Flush(ctx context.Context) error { return nil }
   222  
   223  type hostAgentProfile struct {
   224  	autoprofile.Profile
   225  	ProcessID string `json:"pid"`
   226  }
   227  
   228  // SendProfiles sends profile data to the agent
   229  func (agent *agentS) SendProfiles(profiles []autoprofile.Profile) error {
   230  	agentProfiles := make([]hostAgentProfile, 0, len(profiles))
   231  	for _, p := range profiles {
   232  		agentProfiles = append(agentProfiles, hostAgentProfile{p, agent.from.EntityID})
   233  	}
   234  
   235  	_, err := agent.request(agent.makeURL(agentProfilesURL), "POST", agentProfiles)
   236  	if err != nil {
   237  		agent.logger.Error("failed to send profile data to the host agent: ", err)
   238  		agent.reset()
   239  
   240  		return err
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  func (agent *agentS) setLogger(l LeveledLogger) {
   247  	agent.logger = l
   248  }
   249  
   250  func (agent *agentS) makeURL(prefix string) string {
   251  	return agent.makeHostURL(agent.host, prefix)
   252  }
   253  
   254  func (agent *agentS) makeHostURL(host string, prefix string) string {
   255  	url := "http://" + host + ":" + agent.port + prefix
   256  
   257  	if prefix[len(prefix)-1:] == "." && agent.from.EntityID != "" {
   258  		url += agent.from.EntityID
   259  	}
   260  
   261  	return url
   262  }
   263  
   264  func (agent *agentS) head(url string) (string, error) {
   265  	return agent.request(url, "HEAD", nil)
   266  }
   267  
   268  // request will overwrite the client timeout for a single request
   269  func (agent *agentS) request(url string, method string, data interface{}) (string, error) {
   270  	ctx, cancel := context.WithTimeout(context.Background(), agent.clientTimeout)
   271  	defer cancel()
   272  	return agent.fullRequestResponse(ctx, url, method, data, nil, "")
   273  }
   274  
   275  func (agent *agentS) announceRequest(url string, method string, data interface{}, ret *agentResponse) (string, error) {
   276  	return agent.fullRequestResponse(context.Background(), url, method, data, ret, "")
   277  }
   278  
   279  // requestHeader will overwrite the client timeout for a single request
   280  func (agent *agentS) requestHeader(url string, method string, header string) (string, error) {
   281  	ctx, cancel := context.WithTimeout(context.Background(), agent.clientTimeout)
   282  	defer cancel()
   283  	return agent.fullRequestResponse(ctx, url, method, nil, nil, header)
   284  }
   285  
   286  func (agent *agentS) fullRequestResponse(ctx context.Context, url string, method string, data interface{}, body interface{}, header string) (string, error) {
   287  	var j []byte
   288  	var ret string
   289  	var err error
   290  	var resp *http.Response
   291  	var req *http.Request
   292  
   293  	if data != nil {
   294  		j, err = json.Marshal(data)
   295  	}
   296  
   297  	if err == nil {
   298  		if j != nil {
   299  			b := bytes.NewBuffer(j)
   300  			if b.Len() > maxContentLength {
   301  				agent.logger.Warn(`A batch of spans has been rejected because it is too large to be sent to the agent.`)
   302  
   303  				return "", payloadTooLargeErr
   304  			}
   305  
   306  			req, err = http.NewRequest(method, url, b)
   307  		} else {
   308  			req, err = http.NewRequest(method, url, nil)
   309  		}
   310  
   311  		req := req.WithContext(ctx)
   312  
   313  		// Uncomment this to dump json payloads
   314  		// log.debug(bytes.NewBuffer(j))
   315  
   316  		if err == nil {
   317  			req.Header.Set("Content-Type", "application/json")
   318  			resp, err = agent.client.Do(req)
   319  			if err == nil {
   320  				defer resp.Body.Close()
   321  
   322  				if resp.StatusCode < 200 || resp.StatusCode >= 300 {
   323  					err = errors.New(resp.Status)
   324  				} else {
   325  					if body != nil {
   326  						var b []byte
   327  						b, err = ioutil.ReadAll(resp.Body)
   328  						json.Unmarshal(b, body)
   329  					}
   330  
   331  					if header != "" {
   332  						ret = resp.Header.Get(header)
   333  					}
   334  				}
   335  
   336  				io.CopyN(ioutil.Discard, resp.Body, 256<<10)
   337  			}
   338  		}
   339  	}
   340  
   341  	if err != nil {
   342  		// Ignore errors while in announced stated (before ready) as
   343  		// this is the time where the entity is registering in the Instana
   344  		// backend and it will return 404 until it's done.
   345  		agent.mu.RLock()
   346  		if !agent.fsm.fsm.Is("announced") {
   347  			agent.logger.Info("failed to send a request to ", url, ": ", err)
   348  		}
   349  		agent.mu.RUnlock()
   350  	}
   351  
   352  	return ret, err
   353  }
   354  
   355  func (agent *agentS) applyHostAgentSettings(resp agentResponse) {
   356  	agent.from = newHostAgentFromS(int(resp.Pid), resp.HostID)
   357  
   358  	if resp.Secrets.Matcher != "" {
   359  		m, err := NamedMatcher(resp.Secrets.Matcher, resp.Secrets.List)
   360  		if err != nil {
   361  			agent.logger.Warn("failed to apply secrets matcher configuration: ", err)
   362  		} else {
   363  			sensor.options.Tracer.Secrets = m
   364  		}
   365  	}
   366  
   367  	if len(sensor.options.Tracer.CollectableHTTPHeaders) == 0 {
   368  		sensor.options.Tracer.CollectableHTTPHeaders = resp.getExtraHTTPHeaders()
   369  	}
   370  }
   371  
   372  func (agent *agentS) setHost(host string) {
   373  	agent.host = host
   374  }
   375  
   376  func (agent *agentS) getHost() string {
   377  	return agent.host
   378  }
   379  
   380  func (agent *agentS) reset() {
   381  	agent.mu.Lock()
   382  	agent.fsm.reset()
   383  	agent.mu.Unlock()
   384  }
   385  
   386  func (agent *agentS) logDetailedInformationAboutDroppedSpans(size int, spans []Span, err error) {
   387  	var marshaledSpans []string
   388  	for i := range spans {
   389  		ms, err := json.Marshal(spans[i])
   390  		if err == nil {
   391  			marshaledSpans = append(marshaledSpans, string(ms))
   392  		}
   393  	}
   394  	sort.Slice(marshaledSpans, func(i, j int) bool {
   395  		// descending order
   396  		return len(marshaledSpans[i]) > len(marshaledSpans[j])
   397  	})
   398  
   399  	if size > len(marshaledSpans) {
   400  		size = len(marshaledSpans)
   401  	}
   402  
   403  	agent.logger.Warn(
   404  		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",
   405  			len(spans),
   406  			err.Error(),
   407  			strings.Join(marshaledSpans[:size], ";"),
   408  		),
   409  	)
   410  }