Skip to content

Commit 4a430c8

Browse files
authored
Merge pull request #286 from buildkite/feat_add_max_idle_connections
This change enables configuration of http connection pooling
2 parents 861b62b + 0e08a5d commit 4a430c8

File tree

5 files changed

+106
-35
lines changed

5 files changed

+106
-35
lines changed

README.md

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ It requires a `provided.al2` environment and respects the following env vars:
7373
`Key=Value,Other=Value` containing the Cloudwatch dimensions to index metrics
7474
under.
7575

76+
To adjust timeouts, and connection pooling in the HTTP client use the following env vars:
77+
78+
- `BUILDKITE_AGENT_METRICS_TIMEOUT` : Timeout, in seconds, TLS handshake and idle connections, for HTTP requests, to Buildkite API (default 15).
79+
- `BUILDKITE_AGENT_METRICS_MAX_IDLE_CONNS` : Maximum number of idle (keep-alive) HTTP connections
80+
for Buildkite Agent API. Zero means no limit, -1 disables pooling (default 100).
81+
7682
Additionally, one of the following groups of environment variables must be set
7783
in order to define how the Lambda function should obtain the required Buildkite
7884
Agent API token:
@@ -140,43 +146,47 @@ docker run --rm buildkite-agent-metrics -token abc123 -interval 30s -queue my-qu
140146
$ buildkite-agent-metrics --help
141147
Usage of buildkite-agent-metrics:
142148
-backend string
143-
Specify the backend to use: cloudwatch, statsd, prometheus, stackdriver (default "cloudwatch")
149+
Specify the backend to use: cloudwatch, newrelic, prometheus, stackdriver, statsd (default "cloudwatch")
144150
-cloudwatch-dimensions string
145-
Cloudwatch dimensions to index metrics under, in the form of Key=Value, Other=Value
151+
Cloudwatch dimensions to index metrics under, in the form of Key=Value, Other=Value
146152
-cloudwatch-region string
147-
AWS Region to connect to, defaults to $AWS_REGION or us-east-1
153+
AWS Region to connect to, defaults to $AWS_REGION or us-east-1
148154
-debug
149-
Show debug output
155+
Show debug output
150156
-debug-http
151-
Show full http traces
157+
Show full http traces
152158
-dry-run
153-
Whether to only print metrics
159+
Whether to only print metrics
154160
-endpoint string
155-
A custom Buildkite Agent API endpoint (default "https://agent.buildkite.com/v3")
161+
A custom Buildkite Agent API endpoint (default "https://agent.buildkite.com/v3")
156162
-interval duration
157-
Update metrics every interval, rather than once
163+
Update metrics every interval, rather than once
164+
-max-idle-conns int
165+
Maximum number of idle (keep-alive) HTTP connections for Buildkite Agent API. Zero means no limit, -1 disables connection reuse. (default 100)
158166
-newrelic-app-name string
159-
New Relic application name for metric events
167+
New Relic application name for metric events
160168
-newrelic-license-key string
161-
New Relic license key for publishing events
169+
New Relic license key for publishing events
162170
-prometheus-addr string
163-
Prometheus metrics transport bind address (default ":8080")
171+
Prometheus metrics transport bind address (default ":8080")
164172
-prometheus-path string
165-
Prometheus metrics transport path (default "/metrics")
173+
Prometheus metrics transport path (default "/metrics")
166174
-queue value
167-
Specific queues to process
175+
Specific queues to process
168176
-quiet
169-
Only print errors
177+
Only print errors
170178
-stackdriver-projectid string
171-
Specify Stackdriver Project ID
179+
Specify Stackdriver Project ID
172180
-statsd-host string
173-
Specify the StatsD server (default "127.0.0.1:8125")
181+
Specify the StatsD server (default "127.0.0.1:8125")
174182
-statsd-tags
175-
Whether your StatsD server supports tagging like Datadog
176-
-token string
177-
A Buildkite Agent Registration Token
183+
Whether your StatsD server supports tagging like Datadog
184+
-timeout int
185+
Timeout, in seconds, for HTTP requests to Buildkite API (default 15)
186+
-token value
187+
Buildkite Agent registration tokens. At least one is required. Multiple cluster tokens can be used to gather metrics for multiple clusters.
178188
-version
179-
Show the version
189+
Show the version
180190
```
181191

182192
### Backends

collector/collector.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,18 +407,25 @@ func traceHTTPRequest(req *http.Request) *http.Request {
407407
return req
408408
}
409409

410-
func NewHTTPClient(timeout int) *http.Client {
410+
func NewHTTPClient(timeout, maxIdleConns int) *http.Client {
411411

412412
connectionTimeout := time.Duration(timeout) * time.Second
413413

414414
return &http.Client{
415415
Timeout: connectionTimeout,
416416
Transport: &http.Transport{
417+
MaxIdleConns: maxIdleConns,
418+
IdleConnTimeout: connectionTimeout,
419+
ResponseHeaderTimeout: connectionTimeout,
420+
DisableKeepAlives: false,
417421
Dial: (&net.Dialer{
418422
Timeout: connectionTimeout,
419423
KeepAlive: connectionTimeout,
420424
}).Dial,
421425
TLSHandshakeTimeout: connectionTimeout,
422426
},
427+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
428+
return http.ErrUseLastResponse
429+
},
423430
}
424431
}

lambda/main.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func Handler(ctx context.Context, evt json.RawMessage) (string, error) {
5656
quietString := os.Getenv("BUILDKITE_QUIET")
5757
quiet := quietString == "1" || quietString == "true"
5858
timeout := os.Getenv("BUILDKITE_AGENT_METRICS_TIMEOUT")
59+
maxIdleConns := os.Getenv("BUILDKITE_AGENT_METRICS_MAX_IDLE_CONNS")
5960

6061
debugEnvVar := os.Getenv("BUILDKITE_AGENT_METRICS_DEBUG")
6162
debug := debugEnvVar == "1" || debugEnvVar == "true"
@@ -94,17 +95,17 @@ func Handler(ctx context.Context, evt json.RawMessage) (string, error) {
9495
queues = strings.Split(queue, ",")
9596
}
9697

97-
if timeout == "" {
98-
timeout = "15"
98+
configuredTimeout, err := toIntWithDefault(timeout, 15)
99+
if err != nil {
100+
return "", err
99101
}
100102

101-
configuredTimeout, err := strconv.Atoi(timeout)
102-
103+
configuredMaxIdleConns, err := toIntWithDefault(maxIdleConns, 100) // Default to 100 in line with http.DefaultTransport
103104
if err != nil {
104105
return "", err
105106
}
106107

107-
httpClient := collector.NewHTTPClient(configuredTimeout)
108+
httpClient := collector.NewHTTPClient(configuredTimeout, configuredMaxIdleConns)
108109

109110
userAgent := fmt.Sprintf("buildkite-agent-metrics/%s buildkite-agent-metrics-lambda", version.Version)
110111

@@ -274,3 +275,11 @@ func checkMutuallyExclusiveEnvVars(varNames ...string) error {
274275
return fmt.Errorf("the environment variables [%s] are mutually exclusive", strings.Join(foundVars, ","))
275276
}
276277
}
278+
279+
func toIntWithDefault(val string, defaultVal int) (int, error) {
280+
if val == "" {
281+
return defaultVal, nil
282+
}
283+
284+
return strconv.Atoi(val)
285+
}

lambda/main_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package main
2+
3+
import "testing"
4+
5+
func Test_toIntWithDefault(t *testing.T) {
6+
type args struct {
7+
val string
8+
defaultVal int
9+
}
10+
tests := []struct {
11+
name string
12+
args args
13+
want int
14+
wantErr bool
15+
}{
16+
{
17+
name: "empty",
18+
args: args{val: "", defaultVal: 10},
19+
want: 10,
20+
},
21+
{
22+
name: "invalid",
23+
args: args{val: "invalid", defaultVal: 10},
24+
wantErr: true,
25+
},
26+
{
27+
name: "valid",
28+
args: args{val: "20", defaultVal: 10},
29+
want: 20,
30+
},
31+
}
32+
for _, tt := range tests {
33+
t.Run(tt.name, func(t *testing.T) {
34+
got, err := toIntWithDefault(tt.args.val, tt.args.defaultVal)
35+
if (err != nil) != tt.wantErr {
36+
t.Errorf("toIntWithDefault(%q, %d) error = %v, wantErr %v", tt.args.val, tt.args.defaultVal, err, tt.wantErr)
37+
return
38+
}
39+
if got != tt.want {
40+
t.Errorf("toIntWithDefault(%q, %d) = %v, want %v", tt.args.val, tt.args.defaultVal, got, tt.want)
41+
}
42+
})
43+
}
44+
}

main.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ var metricsBackend backend.Backend
2020

2121
func main() {
2222
var (
23-
interval = flag.Duration("interval", 0, "Update metrics every interval, rather than once")
24-
showVersion = flag.Bool("version", false, "Show the version")
25-
quiet = flag.Bool("quiet", false, "Only print errors")
26-
debug = flag.Bool("debug", false, "Show debug output")
27-
debugHttp = flag.Bool("debug-http", false, "Show full http traces")
28-
dryRun = flag.Bool("dry-run", false, "Whether to only print metrics")
29-
endpoint = flag.String("endpoint", "https://agent.buildkite.com/v3", "A custom Buildkite Agent API endpoint")
30-
timeout = flag.Int("timeout", 15, "Timeout, in seconds, for HTTP requests to Buildkite API")
23+
interval = flag.Duration("interval", 0, "Update metrics every interval, rather than once")
24+
showVersion = flag.Bool("version", false, "Show the version")
25+
quiet = flag.Bool("quiet", false, "Only print errors")
26+
debug = flag.Bool("debug", false, "Show debug output")
27+
debugHttp = flag.Bool("debug-http", false, "Show full http traces")
28+
dryRun = flag.Bool("dry-run", false, "Whether to only print metrics")
29+
endpoint = flag.String("endpoint", "https://agent.buildkite.com/v3", "A custom Buildkite Agent API endpoint")
30+
timeout = flag.Int("timeout", 15, "Timeout, in seconds, TLS handshake and idle connections, for HTTP requests, to Buildkite API")
31+
maxIdleConns = flag.Int("max-idle-conns", 100, "Maximum number of idle (keep-alive) HTTP connections for Buildkite Agent API. Zero means no limit, -1 disables connection reuse.")
3132

3233
// backend config
3334
backendOpt = flag.String("backend", "cloudwatch", "Specify the backend to use: cloudwatch, newrelic, prometheus, stackdriver, statsd")
@@ -141,7 +142,7 @@ func main() {
141142
}
142143
}
143144

144-
httpClient := collector.NewHTTPClient(*timeout)
145+
httpClient := collector.NewHTTPClient(*timeout, *maxIdleConns)
145146

146147
collectors := make([]*collector.Collector, 0, len(tokens))
147148
for _, token := range tokens {

0 commit comments

Comments
 (0)