Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/supportbundle/supportbundle.go
4093 views
1
package supportbundle
2
3
import (
4
"archive/zip"
5
"bytes"
6
"context"
7
"fmt"
8
"io"
9
"net/http"
10
"path/filepath"
11
"runtime"
12
"runtime/pprof"
13
"strings"
14
"sync"
15
"time"
16
17
"github.com/grafana/agent/pkg/build"
18
"github.com/grafana/agent/pkg/server"
19
"github.com/mackerelio/go-osstat/uptime"
20
"gopkg.in/yaml.v3"
21
)
22
23
// Bundle collects all the data that is exposed as a support bundle.
24
type Bundle struct {
25
meta []byte
26
config []byte
27
agentMetrics []byte
28
agentMetricsInstances []byte
29
agentMetricsTargets []byte
30
agentLogsInstances []byte
31
agentLogsTargets []byte
32
heapBuf *bytes.Buffer
33
goroutineBuf *bytes.Buffer
34
blockBuf *bytes.Buffer
35
mutexBuf *bytes.Buffer
36
cpuBuf *bytes.Buffer
37
}
38
39
// Metadata contains general runtime information about the current Agent.
40
type Metadata struct {
41
BuildVersion string `yaml:"build_version"`
42
OS string `yaml:"os"`
43
Architecture string `yaml:"architecture"`
44
Uptime float64 `yaml:"uptime"`
45
Payload map[string]interface{} `yaml:"payload"`
46
}
47
48
// Used to enforce single-flight requests to Export
49
var mut sync.Mutex
50
51
// Export gathers the information required for the support bundle.
52
func Export(ctx context.Context, enabledFeatures []string, cfg []byte, srvAddress string, dialContext server.DialContextFunc) (*Bundle, error) {
53
mut.Lock()
54
defer mut.Unlock()
55
// The block profiler is disabled by default. Temporarily enable recording
56
// of all blocking events. Also, temporarily record all mutex contentions,
57
// and defer restoring of earlier mutex profiling fraction.
58
runtime.SetBlockProfileRate(1)
59
old := runtime.SetMutexProfileFraction(1)
60
defer func() {
61
runtime.SetBlockProfileRate(0)
62
runtime.SetMutexProfileFraction(old)
63
}()
64
65
// Gather runtime metadata.
66
ut, err := uptime.Get()
67
if err != nil {
68
return nil, err
69
}
70
m := Metadata{
71
BuildVersion: build.Version,
72
OS: runtime.GOOS,
73
Architecture: runtime.GOARCH,
74
Uptime: ut.Seconds(),
75
Payload: map[string]interface{}{"enabled-features": enabledFeatures},
76
}
77
meta, err := yaml.Marshal(m)
78
if err != nil {
79
return nil, fmt.Errorf("failed to marshal support bundle metadata: %s", err)
80
}
81
82
var httpClient http.Client
83
httpClient.Transport = &http.Transport{DialContext: dialContext}
84
// Gather Agent's own metrics.
85
resp, err := httpClient.Get("http://" + srvAddress + "/metrics")
86
if err != nil {
87
return nil, fmt.Errorf("failed to get internal Agent metrics: %s", err)
88
}
89
agentMetrics, err := io.ReadAll(resp.Body)
90
if err != nil {
91
return nil, fmt.Errorf("failed to read internal Agent metrics: %s", err)
92
}
93
94
// Collect the Agent metrics instances and target statuses.
95
resp, err = httpClient.Get("http://" + srvAddress + "/agent/api/v1/metrics/instances")
96
if err != nil {
97
return nil, fmt.Errorf("failed to get internal Agent metrics: %s", err)
98
}
99
agentMetricsInstances, err := io.ReadAll(resp.Body)
100
if err != nil {
101
return nil, fmt.Errorf("failed to read internal Agent metrics: %s", err)
102
}
103
resp, err = httpClient.Get("http://" + srvAddress + "/agent/api/v1/metrics/targets")
104
if err != nil {
105
return nil, fmt.Errorf("failed to get Agent metrics targets: %s", err)
106
}
107
agentMetricsTargets, err := io.ReadAll(resp.Body)
108
if err != nil {
109
return nil, fmt.Errorf("failed to read Agent metrics targets: %s", err)
110
}
111
112
// Collect the Agent's logs instances and target statuses.
113
resp, err = httpClient.Get("http://" + srvAddress + "/agent/api/v1/logs/instances")
114
if err != nil {
115
return nil, fmt.Errorf("failed to get Agent logs instances: %s", err)
116
}
117
agentLogsInstances, err := io.ReadAll(resp.Body)
118
if err != nil {
119
return nil, fmt.Errorf("failed to read Agent logs instances: %s", err)
120
}
121
122
resp, err = http.DefaultClient.Get("http://" + srvAddress + "/agent/api/v1/logs/targets")
123
if err != nil {
124
return nil, fmt.Errorf("failed to get Agent logs targets: %s", err)
125
}
126
agentLogsTargets, err := io.ReadAll(resp.Body)
127
if err != nil {
128
return nil, fmt.Errorf("failed to read Agent logs targets: %s", err)
129
}
130
131
// Export pprof data.
132
var (
133
cpuBuf bytes.Buffer
134
heapBuf bytes.Buffer
135
goroutineBuf bytes.Buffer
136
blockBuf bytes.Buffer
137
mutexBuf bytes.Buffer
138
)
139
err = pprof.StartCPUProfile(&cpuBuf)
140
if err != nil {
141
return nil, err
142
}
143
deadline, _ := ctx.Deadline()
144
// Sleep for the remaining of the context deadline, but leave some time for
145
// the rest of the bundle to be exported successfully.
146
time.Sleep(time.Until(deadline) - 200*time.Millisecond)
147
pprof.StopCPUProfile()
148
149
p := pprof.Lookup("heap")
150
if err := p.WriteTo(&heapBuf, 0); err != nil {
151
return nil, err
152
}
153
p = pprof.Lookup("goroutine")
154
if err := p.WriteTo(&goroutineBuf, 0); err != nil {
155
return nil, err
156
}
157
p = pprof.Lookup("block")
158
if err := p.WriteTo(&blockBuf, 0); err != nil {
159
return nil, err
160
}
161
p = pprof.Lookup("mutex")
162
if err := p.WriteTo(&mutexBuf, 0); err != nil {
163
return nil, err
164
}
165
166
// Finally, bundle everything up to be served, either as a zip from
167
// memory, or exported to a directory.
168
bundle := &Bundle{
169
meta: meta,
170
config: cfg,
171
agentMetrics: agentMetrics,
172
agentMetricsInstances: agentMetricsInstances,
173
agentMetricsTargets: agentMetricsTargets,
174
agentLogsInstances: agentLogsInstances,
175
agentLogsTargets: agentLogsTargets,
176
heapBuf: &heapBuf,
177
goroutineBuf: &goroutineBuf,
178
blockBuf: &blockBuf,
179
mutexBuf: &mutexBuf,
180
cpuBuf: &cpuBuf,
181
}
182
183
return bundle, nil
184
}
185
186
// Serve the collected data and logs as a zip file over the given
187
// http.ResponseWriter.
188
func Serve(rw http.ResponseWriter, b *Bundle, logsBuf *bytes.Buffer) error {
189
zw := zip.NewWriter(rw)
190
rw.Header().Set("Content-Type", "application/zip")
191
rw.Header().Set("Content-Disposition", "attachment; filename=\"agent-support-bundle.zip\"")
192
193
zipStructure := map[string][]byte{
194
"agent-metadata.yaml": b.meta,
195
"agent-config.yaml": b.config,
196
"agent-metrics.txt": b.agentMetrics,
197
"agent-metrics-instances.json": b.agentMetricsInstances,
198
"agent-metrics-targets.json": b.agentMetricsTargets,
199
"agent-logs-instances.json": b.agentLogsInstances,
200
"agent-logs-targets.json": b.agentLogsTargets,
201
"agent-logs.txt": logsBuf.Bytes(),
202
"pprof/cpu.pprof": b.cpuBuf.Bytes(),
203
"pprof/heap.pprof": b.heapBuf.Bytes(),
204
"pprof/goroutine.pprof": b.goroutineBuf.Bytes(),
205
"pprof/mutex.pprof": b.mutexBuf.Bytes(),
206
"pprof/block.pprof": b.blockBuf.Bytes(),
207
}
208
209
for fn, b := range zipStructure {
210
if b != nil {
211
path := append([]string{"agent-support-bundle"}, strings.Split(fn, "/")...)
212
if err := writeByteSlice(zw, b, path...); err != nil {
213
return err
214
}
215
}
216
}
217
218
err := zw.Close()
219
if err != nil {
220
return fmt.Errorf("failed to flush the zip writer: %v", err)
221
}
222
return nil
223
}
224
225
func writeByteSlice(zw *zip.Writer, b []byte, fn ...string) error {
226
f, err := zw.Create(filepath.Join(fn...))
227
if err != nil {
228
return err
229
}
230
_, err = f.Write(b)
231
if err != nil {
232
return err
233
}
234
return nil
235
}
236
237