-
Notifications
You must be signed in to change notification settings - Fork 39
/
generic_serverless_agent.go
172 lines (132 loc) · 4.02 KB
/
generic_serverless_agent.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// (c) Copyright IBM Corp. 2024
package instana
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"sync"
"time"
"github.com/google/uuid"
"github.com/instana/go-sensor/acceptor"
"github.com/instana/go-sensor/autoprofile"
)
const (
flushPeriodForGenericInSec = 2
)
type genericServerlessAgent struct {
Endpoint string
Key string
PluginName string
PID int
snapshot serverlessSnapshot
mu sync.Mutex
spanQueue []Span
client *http.Client
logger LeveledLogger
}
func newGenericServerlessAgent(acceptorEndpoint, agentKey string, client *http.Client, logger LeveledLogger) *genericServerlessAgent {
if logger == nil {
logger = defaultLogger
}
if client == nil {
client = http.DefaultClient
// You can change this timeout by setting the INSTANA_TIMEOUT environment variable.
client.Timeout = 2 * time.Second
}
logger.Debug("initializing generic serverless agent")
// Creating a unique serverless host ID.
uniqHostId := "Generic_Serverless_Agent" + uuid.New().String()
agent := &genericServerlessAgent{
Endpoint: acceptorEndpoint,
Key: agentKey,
PID: os.Getpid(),
client: client,
logger: logger,
snapshot: serverlessSnapshot{
Host: uniqHostId,
EntityID: uniqHostId,
},
}
go func() {
t := time.NewTicker(flushPeriodForGenericInSec * time.Second)
defer t.Stop()
for range t.C {
if err := agent.Flush(context.Background()); err != nil {
agent.logger.Error("failed to post collected data: ", err)
}
}
}()
return agent
}
func (a *genericServerlessAgent) Ready() bool { return true }
func (a *genericServerlessAgent) SendMetrics(acceptor.Metrics) error { return nil }
func (a *genericServerlessAgent) SendEvent(*EventData) error { return nil }
func (a *genericServerlessAgent) SendSpans(spans []Span) error {
a.enqueueSpans(spans)
return nil
}
func (a *genericServerlessAgent) SendProfiles([]autoprofile.Profile) error { return nil }
func (a *genericServerlessAgent) Flush(ctx context.Context) error {
from := newServerlessAgentFromS(a.snapshot.EntityID, "generic_serverless")
payload := struct {
Spans []Span `json:"spans,omitempty"`
}{}
a.mu.Lock()
payload.Spans = make([]Span, len(a.spanQueue))
copy(payload.Spans, a.spanQueue)
a.spanQueue = a.spanQueue[:0]
a.mu.Unlock()
for i := range payload.Spans {
payload.Spans[i].From = from
}
buf := bytes.NewBuffer(nil)
if err := json.NewEncoder(buf).Encode(payload); err != nil {
return fmt.Errorf("failed to marshal traces payload: %s", err)
}
payloadSize := buf.Len()
if payloadSize > maxContentLength {
a.logger.Warn(fmt.Sprintf("failed to send the spans. Payload size: %d exceeded max size: %d", payloadSize, maxContentLength))
return payloadTooLargeErr
}
req, err := http.NewRequest(http.MethodPost, a.Endpoint+"/bundle", buf)
if err != nil {
a.enqueueSpans(payload.Spans)
return fmt.Errorf("failed to prepare send traces request: %s", err)
}
req.Header.Set("Content-Type", "application/json")
if err := a.sendRequest(req.WithContext(ctx)); err != nil {
a.enqueueSpans(payload.Spans)
return fmt.Errorf("failed to send traces, will retry later: %dsec. Error details: %s",
flushPeriodForGenericInSec, err.Error())
}
return nil
}
func (a *genericServerlessAgent) enqueueSpans(spans []Span) {
a.mu.Lock()
defer a.mu.Unlock()
a.spanQueue = append(a.spanQueue, spans...)
}
func (a *genericServerlessAgent) sendRequest(req *http.Request) error {
req.Header.Set("X-Instana-Host", a.snapshot.Host)
req.Header.Set("X-Instana-Key", a.Key)
resp, err := a.client.Do(req)
if err != nil {
return fmt.Errorf("failed to send request to the serverless agent: %s", err)
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
respBody, err := io.ReadAll(resp.Body)
if err != nil {
a.logger.Debug("failed to read serverless agent response: ", err.Error())
return err
}
a.logger.Info("serverless agent has responded with ", resp.Status, ": ", string(respBody))
return err
}
io.CopyN(io.Discard, resp.Body, 1<<20)
return nil
}