-
Notifications
You must be signed in to change notification settings - Fork 414
/
options.go
401 lines (363 loc) · 12 KB
/
options.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016 Datadog, Inc.
package profiler
import (
"context"
"fmt"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"unicode"
"gopkg.in/DataDog/dd-trace-go.v1/internal"
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
"github.com/DataDog/datadog-go/v5/statsd"
)
const (
// DefaultMutexFraction specifies the mutex profile fraction to be used with the mutex profiler.
// For more information or for changing this value, check MutexProfileFraction
DefaultMutexFraction = 10
// DefaultBlockRate specifies the default block profiling rate used by the
// block profiler. For more information or for changing this value, check
// BlockProfileRate. The default rate is chosen to prevent high overhead
// based on the research from:
// https://github.com/felixge/go-profiler-notes/blob/main/block.md#benchmarks
DefaultBlockRate = 10000
// DefaultPeriod specifies the default period at which profiles will be collected.
DefaultPeriod = time.Minute
// DefaultDuration specifies the default length of the CPU profile snapshot.
DefaultDuration = time.Second * 15
// DefaultUploadTimeout specifies the default timeout for uploading profiles.
// It can be overwritten using the DD_PROFILING_UPLOAD_TIMEOUT env variable
// or the WithUploadTimeout option.
DefaultUploadTimeout = 10 * time.Second
)
const (
defaultAPIURL = "https://intake.profile.datadoghq.com/v1/input"
defaultAgentHost = "localhost"
defaultAgentPort = "8126"
defaultEnv = "none"
)
var defaultClient = &http.Client{
// We copy the transport to avoid using the default one, as it might be
// augmented with tracing and we don't want these calls to be recorded.
// See https://golang.org/pkg/net/http/#DefaultTransport .
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
var defaultProfileTypes = []ProfileType{MetricsProfile, CPUProfile, HeapProfile}
type config struct {
apiKey string
agentless bool
// targetURL is the upload destination URL. It will be set by the profiler on start to either apiURL or agentURL
// based on the other options.
targetURL string
apiURL string // apiURL is the Datadog intake API URL
agentURL string // agentURL is the Datadog agent profiling URL
service, env string
hostname string
statsd StatsdClient
httpClient *http.Client
tags []string
types map[ProfileType]struct{}
period time.Duration
cpuDuration time.Duration
uploadTimeout time.Duration
maxGoroutinesWait int
mutexFraction int
blockRate int
outputDir string
}
func urlForSite(site string) (string, error) {
u := fmt.Sprintf("https://intake.profile.%s/v1/input", site)
_, err := url.Parse(u)
return u, err
}
// isAPIKeyValid reports whether the given string is a structurally valid API key
func isAPIKeyValid(key string) bool {
if len(key) != 32 {
return false
}
for _, c := range key {
if c > unicode.MaxASCII || (!unicode.IsLower(c) && !unicode.IsNumber(c)) {
return false
}
}
return true
}
func (c *config) addProfileType(t ProfileType) {
if c.types == nil {
c.types = make(map[ProfileType]struct{})
}
c.types[t] = struct{}{}
}
func defaultConfig() (*config, error) {
c := config{
env: defaultEnv,
apiURL: defaultAPIURL,
service: filepath.Base(os.Args[0]),
statsd: &statsd.NoOpClient{},
httpClient: defaultClient,
period: DefaultPeriod,
cpuDuration: DefaultDuration,
blockRate: DefaultBlockRate,
mutexFraction: DefaultMutexFraction,
uploadTimeout: DefaultUploadTimeout,
maxGoroutinesWait: 1000, // arbitrary value, should limit STW to ~30ms
tags: []string{fmt.Sprintf("pid:%d", os.Getpid())},
}
for _, t := range defaultProfileTypes {
c.addProfileType(t)
}
agentHost, agentPort := defaultAgentHost, defaultAgentPort
if v := os.Getenv("DD_AGENT_HOST"); v != "" {
agentHost = v
}
if v := os.Getenv("DD_TRACE_AGENT_PORT"); v != "" {
agentPort = v
}
WithAgentAddr(net.JoinHostPort(agentHost, agentPort))(&c)
if v := os.Getenv("DD_PROFILING_UPLOAD_TIMEOUT"); v != "" {
d, err := time.ParseDuration(v)
if err != nil {
return nil, fmt.Errorf("DD_PROFILING_UPLOAD_TIMEOUT: %s", err)
}
WithUploadTimeout(d)(&c)
}
if v := os.Getenv("DD_API_KEY"); v != "" {
WithAPIKey(v)(&c)
}
if internal.BoolEnv("DD_PROFILING_AGENTLESS", false) {
WithAgentlessUpload()(&c)
}
if v := os.Getenv("DD_SITE"); v != "" {
WithSite(v)(&c)
}
if v := os.Getenv("DD_ENV"); v != "" {
WithEnv(v)(&c)
}
if v := os.Getenv("DD_SERVICE"); v != "" {
WithService(v)(&c)
}
if v := os.Getenv("DD_VERSION"); v != "" {
WithVersion(v)(&c)
}
if v := os.Getenv("DD_TAGS"); v != "" {
sep := " "
if strings.Index(v, ",") > -1 {
// falling back to comma as separator
sep = ","
}
for _, tag := range strings.Split(v, sep) {
tag = strings.TrimSpace(tag)
if tag == "" {
continue
}
WithTags(tag)(&c)
}
}
WithTags(
"profiler_version:"+version.Tag,
"runtime_version:"+strings.TrimPrefix(runtime.Version(), "go"),
"runtime_compiler:"+runtime.Compiler,
"runtime_arch:"+runtime.GOARCH,
"runtime_os:"+runtime.GOOS,
"runtime-id:"+globalconfig.RuntimeID(),
)(&c)
// not for public use
if v := os.Getenv("DD_PROFILING_URL"); v != "" {
WithURL(v)(&c)
}
// not for public use
if v := os.Getenv("DD_PROFILING_OUTPUT_DIR"); v != "" {
withOutputDir(v)(&c)
}
if v := os.Getenv("DD_PROFILING_WAIT_PROFILE_MAX_GOROUTINES"); v != "" {
n, err := strconv.Atoi(v)
if err != nil {
return nil, fmt.Errorf("DD_PROFILING_WAIT_PROFILE_MAX_GOROUTINES: %s", err)
}
c.maxGoroutinesWait = n
}
return &c, nil
}
// An Option is used to configure the profiler's behaviour.
type Option func(*config)
// WithAgentAddr specifies the address to use when reaching the Datadog Agent.
func WithAgentAddr(hostport string) Option {
return func(cfg *config) {
cfg.agentURL = "http://" + hostport + "/profiling/v1/input"
}
}
// WithAPIKey sets the Datadog API Key and takes precedence over the DD_API_KEY
// env variable. Historically this option was used to enable agentless
// uploading, but as of dd-trace-go v1.30.0 the behavior has changed to always
// default to agent based uploading which doesn't require an API key. So if you
// currently don't have an agent running on the default localhost:8126 hostport
// you need to set it up, or use WithAgentAddr to specify the hostport location
// of the agent. See WithAgentlessUpload for more information.
func WithAPIKey(key string) Option {
return func(cfg *config) {
cfg.apiKey = key
}
}
// WithAgentlessUpload is currently for internal usage only and not officially
// supported. You should not enable it unless somebody at Datadog instructed
// you to do so. It allows to skip the agent and talk to the Datadog API
// directly using the provided API key.
func WithAgentlessUpload() Option {
return func(cfg *config) {
cfg.agentless = true
}
}
// WithURL specifies the HTTP URL for the Datadog Profiling API.
func WithURL(url string) Option {
return func(cfg *config) {
cfg.apiURL = url
}
}
// WithPeriod specifies the interval at which to collect profiles.
func WithPeriod(d time.Duration) Option {
return func(cfg *config) {
cfg.period = d
}
}
// CPUDuration specifies the length at which to collect CPU profiles.
func CPUDuration(d time.Duration) Option {
return func(cfg *config) {
cfg.cpuDuration = d
}
}
// MutexProfileFraction turns on mutex profiles with rate indicating the fraction
// of mutex contention events reported in the mutex profile.
// On average, 1/rate events are reported.
// Setting an aggressive rate can hurt performance.
// For more information on this value, check runtime.SetMutexProfileFraction.
func MutexProfileFraction(rate int) Option {
return func(cfg *config) {
cfg.addProfileType(MutexProfile)
cfg.mutexFraction = rate
}
}
// BlockProfileRate turns on block profiles with the given rate.
// The profiler samples an average of one blocking event per rate nanoseconds spent blocked.
// For example, set rate to 1000000000 (aka int(time.Second.Nanoseconds())) to
// record one sample per second a goroutine is blocked.
// A rate of 1 catches every event.
// Setting an aggressive rate can hurt performance.
// For more information on this value, check runtime.SetBlockProfileRate.
func BlockProfileRate(rate int) Option {
return func(cfg *config) {
cfg.addProfileType(BlockProfile)
cfg.blockRate = rate
}
}
// WithProfileTypes specifies the profile types to be collected by the profiler.
func WithProfileTypes(types ...ProfileType) Option {
return func(cfg *config) {
// reset the types and only use what the user has specified
for k := range cfg.types {
delete(cfg.types, k)
}
cfg.addProfileType(MetricsProfile) // always report metrics
for _, t := range types {
cfg.addProfileType(t)
}
}
}
// WithService specifies the service name to attach to a profile.
func WithService(name string) Option {
return func(cfg *config) {
cfg.service = name
}
}
// WithEnv specifies the environment to which these profiles should be registered.
func WithEnv(env string) Option {
return func(cfg *config) {
cfg.env = env
}
}
// WithVersion specifies the service version tag to attach to profiles
func WithVersion(version string) Option {
return WithTags("version:" + version)
}
// WithTags specifies a set of tags to be attached to the profiler. These may help
// filter the profiling view based on various information.
func WithTags(tags ...string) Option {
return func(cfg *config) {
cfg.tags = append(cfg.tags, tags...)
}
}
// WithStatsd specifies an optional statsd client to use for metrics. By default,
// no metrics are sent.
func WithStatsd(client StatsdClient) Option {
return func(cfg *config) {
cfg.statsd = client
}
}
// WithUploadTimeout specifies the timeout to use for uploading profiles. The
// default timeout is specified by DefaultUploadTimeout or the
// DD_PROFILING_UPLOAD_TIMEOUT env variable. Using a negative value or 0 will
// cause an error when starting the profiler.
func WithUploadTimeout(d time.Duration) Option {
return func(cfg *config) {
cfg.uploadTimeout = d
}
}
// WithSite specifies the datadog site (datadoghq.com, datadoghq.eu, etc.)
// which profiles will be sent to.
func WithSite(site string) Option {
return func(cfg *config) {
u, err := urlForSite(site)
if err != nil {
log.Error("profiler: invalid site provided, using %s (%s)", defaultAPIURL, err)
return
}
cfg.apiURL = u
}
}
// WithHTTPClient specifies the HTTP client to use when submitting profiles to Site.
// In general, using this method is only necessary if you have need to customize the
// transport layer, for instance when using a unix domain socket.
func WithHTTPClient(client *http.Client) Option {
return func(cfg *config) {
cfg.httpClient = client
}
}
// WithUDS configures the HTTP client to dial the Datadog Agent via the specified Unix Domain Socket path.
func WithUDS(socketPath string) Option {
return WithHTTPClient(&http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
})
}
// withOutputDir writes a copy of all uploaded profiles to the given
// directory. This is intended for local development or debugging uploading
// issues. The directory will keep growing, no cleanup is performed.
func withOutputDir(dir string) Option {
return func(cfg *config) {
cfg.outputDir = dir
}
}