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 }