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 }