internal/pkg/config/env_defaults.go (277 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.
package config
import (
"embed"
"fmt"
"io"
"io/fs"
"math"
"runtime"
"strings"
"time"
"github.com/pbnjay/memory"
"github.com/rs/zerolog"
"github.com/elastic/go-ucfg/yaml"
)
const (
defaultCacheNumCounters = 500000 // 10x times expected count
defaultCacheMaxCost = 50 * 1024 * 1024 // 50MiB cache size
defaultMaxConnections = 0 // no limit
defaultActionInterval = 0 // no throttle
defaultActionBurst = 5
defaultPolicyInterval = time.Millisecond * 5
defaultPolicyBurst = 1 // NOTE: burst 1 keeps the same behaviour as the previous throttle
defaultCheckinInterval = time.Millisecond
defaultCheckinBurst = 1000
defaultCheckinMax = 0
defaultCheckinMaxBody = 1024 * 1024
defaultArtifactInterval = time.Millisecond * 5
defaultArtifactBurst = 25
defaultArtifactMax = 50
defaultArtifactMaxBody = 0
defaultEnrollInterval = time.Millisecond * 10
defaultEnrollBurst = 50
defaultEnrollMax = 100
defaultEnrollMaxBody = 1024 * 512
defaultAckInterval = time.Millisecond * 10
defaultAckBurst = 50
defaultAckMax = 100
defaultAckMaxBody = 1024 * 1024 * 2
defaultStatusInterval = time.Millisecond * 5
defaultStatusBurst = 25
defaultStatusMax = 50
defaultStatusMaxBody = 0
defaultUploadStartInterval = time.Second * 2
defaultUploadStartBurst = 5
defaultUploadStartMax = 10
defaultUploadStartMaxBody = 1024 * 1024 * 5
defaultUploadEndInterval = time.Second * 2
defaultUploadEndBurst = 5
defaultUploadEndMax = 10
defaultUploadEndMaxBody = 1024
defaultUploadChunkInterval = time.Millisecond * 3
defaultUploadChunkBurst = 5
defaultUploadChunkMax = 10
defaultUploadChunkMaxBody = 1024 * 1024 * 4 // this is also enforced in handler, a chunk MAY NOT be larger than 4 MiB
defaultFileDelivInterval = time.Millisecond * 100
defaultFileDelivBurst = 5
defaultFileDelivMax = 10
defaultFileDelivMaxBody = 0
defaultPGPRetrievalInterval = time.Millisecond * 5
defaultPGPRetrievalBurst = 25
defaultPGPRetrievalMax = 50
defaultPGPRetrievalMaxBody = 0
defaultAuditUnenrollInterval = time.Millisecond * 10
defaultAuditUnenrollBurst = 50
defaultAuditUnenrollMax = 100
defaultAuditUnenrollMaxBody = 1024
)
type valueRange struct {
Min int `config:"min"`
Max int `config:"max"`
}
type envLimits struct {
Agents valueRange `config:"num_agents"`
RecommendedRAM uint64 `config:"recommended_min_ram"`
Server *serverLimitDefaults `config:"server_limits"`
Cache *cacheLimits `config:"cache_limits"`
}
func defaultEnvLimits() *envLimits {
return &envLimits{
Agents: valueRange{
Min: 0,
Max: int(getMaxInt()),
},
Server: defaultserverLimitDefaults(),
Cache: defaultCacheLimits(),
}
}
type cacheLimits struct {
NumCounters int64 `config:"num_counters"`
MaxCost int64 `config:"max_cost"`
}
func defaultCacheLimits() *cacheLimits {
return &cacheLimits{
NumCounters: defaultCacheNumCounters,
MaxCost: defaultCacheMaxCost,
}
}
type limit struct {
// Interval is the rate limiter's max frequency of requests (1s means 1req/s, 1ms means 1req/ms)
// A rate of 0 disables the rate limiter
Interval time.Duration `config:"interval"`
// Burst is the rate limiter's burst allocation that allows for spikes of traffic.
// Having a burst value > max is functionally setting it to the same as max.
// A burst of 0 allows no requests.
Burst int `config:"burst"`
// Max is the total number of requests allowed to an endpoint.
// A zero value disables the max limiter
Max int64 `config:"max"`
// MaxBody is the request body size limit.
// Used in the ack, checkin, and enroll endpoints.
// A zero value disabled the check.
MaxBody int64 `config:"max_body_byte_size"`
}
type serverLimitDefaults struct {
PolicyThrottle time.Duration `config:"policy_throttle"` // deprecated: replaced by policy_limit
MaxConnections int `config:"max_connections"`
ActionLimit limit `config:"action_limit"`
PolicyLimit limit `config:"policy_limit"`
CheckinLimit limit `config:"checkin_limit"`
ArtifactLimit limit `config:"artifact_limit"`
EnrollLimit limit `config:"enroll_limit"`
AckLimit limit `config:"ack_limit"`
StatusLimit limit `config:"status_limit"`
UploadStartLimit limit `config:"upload_start_limit"`
UploadEndLimit limit `config:"upload_end_limit"`
UploadChunkLimit limit `config:"upload_chunk_limit"`
DeliverFileLimit limit `config:"file_delivery_limit"`
GetPGPKeyLimit limit `config:"pgp_retrieval_limit"`
AuditUnenrollLimit limit `config:"audit_unenroll_limit"`
}
func defaultserverLimitDefaults() *serverLimitDefaults {
return &serverLimitDefaults{
MaxConnections: defaultMaxConnections,
ActionLimit: limit{
Interval: defaultActionInterval,
Burst: defaultActionBurst,
},
PolicyLimit: limit{
Interval: defaultPolicyInterval,
Burst: defaultPolicyBurst,
},
CheckinLimit: limit{
Interval: defaultCheckinInterval,
Burst: defaultCheckinBurst,
Max: defaultCheckinMax,
MaxBody: defaultCheckinMaxBody,
},
ArtifactLimit: limit{
Interval: defaultArtifactInterval,
Burst: defaultArtifactBurst,
Max: defaultArtifactMax,
MaxBody: defaultArtifactMaxBody,
},
EnrollLimit: limit{
Interval: defaultEnrollInterval,
Burst: defaultEnrollBurst,
Max: defaultEnrollMax,
MaxBody: defaultEnrollMaxBody,
},
AckLimit: limit{
Interval: defaultAckInterval,
Burst: defaultAckBurst,
Max: defaultAckMax,
MaxBody: defaultAckMaxBody,
},
StatusLimit: limit{
Interval: defaultStatusInterval,
Burst: defaultStatusBurst,
Max: defaultStatusMax,
MaxBody: defaultStatusMaxBody,
},
UploadStartLimit: limit{
Interval: defaultUploadStartInterval,
Burst: defaultUploadStartBurst,
Max: defaultUploadStartMax,
MaxBody: defaultUploadStartMaxBody,
},
UploadEndLimit: limit{
Interval: defaultUploadEndInterval,
Burst: defaultUploadEndBurst,
Max: defaultUploadEndMax,
MaxBody: defaultUploadEndMaxBody,
},
UploadChunkLimit: limit{
Interval: defaultUploadChunkInterval,
Burst: defaultUploadChunkBurst,
Max: defaultUploadChunkMax,
MaxBody: defaultUploadChunkMaxBody,
},
DeliverFileLimit: limit{
Interval: defaultFileDelivInterval,
Burst: defaultFileDelivBurst,
Max: defaultFileDelivMax,
MaxBody: defaultFileDelivMaxBody,
},
GetPGPKeyLimit: limit{
Interval: defaultPGPRetrievalInterval,
Burst: defaultPGPRetrievalBurst,
Max: defaultPGPRetrievalMax,
MaxBody: defaultPGPRetrievalMaxBody,
},
AuditUnenrollLimit: limit{
Interval: defaultAuditUnenrollInterval,
Burst: defaultAuditUnenrollBurst,
Max: defaultAuditUnenrollMax,
MaxBody: defaultAuditUnenrollMaxBody,
},
}
}
var defaults []*envLimits
//go:embed defaults/*.yml
var defaultsFS embed.FS
func init() {
err := fs.WalkDir(defaultsFS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
f, err := defaultsFS.Open(path)
if err != nil {
return fmt.Errorf("unable to open embedded file %s: %w", path, err)
}
p, err := io.ReadAll(f)
if err != nil {
return fmt.Errorf("unable to read embedded file %s: %w", path, err)
}
cfg, err := yaml.NewConfig(p, DefaultOptions...)
if err != nil {
return fmt.Errorf("cannot read spec from %s: %w", path, err)
}
l := defaultEnvLimits()
if err := cfg.Unpack(&l, DefaultOptions...); err != nil {
return fmt.Errorf("cannot unpack spec from %s: %w", path, err)
}
defaults = append(defaults, l)
return nil
})
if err != nil {
panic(err)
}
}
// loadLimits loads cache and server_limit settings based on the passed agentLimit number.
// If agentLimit < 0 the default settings are used.
// If agentLimit > 0 the settings from the matching default/*.yml file are used based off agent count.
// If agentLimit == 0 then the settings from default/*.yml are used based off system memory.
// If a lookup fails, default settings are used.
func loadLimits(log *zerolog.Logger, agentLimit int) *envLimits {
if agentLimit < 0 {
return defaultEnvLimits()
} else if agentLimit == 0 {
return memEnvLimits(log)
}
for _, l := range defaults {
// get nearest limits for configured agent numbers
if l.Agents.Min <= agentLimit && agentLimit <= l.Agents.Max {
log.Info().Msgf("Using system limits for %d to %d agents for a configured value of %d agents", l.Agents.Min, l.Agents.Max, agentLimit)
ramSize := memory.TotalMemory() / 1024 / 1024
if ramSize < l.RecommendedRAM {
log.Warn().Msgf("Detected %d MB of system RAM, which is lower than the recommended amount (%d MB) for the configured agent limit", ramSize, l.RecommendedRAM)
}
return l
}
}
log.Info().Msgf("No applicable limit for %d agents, using default.", agentLimit)
return defaultEnvLimits()
}
// memMB returns the system total memory in MB
// It wraps memory.TotalMemory() so that we can replace the var in unit tests.
var memMB func() uint64 = func() uint64 {
return memory.TotalMemory() / 1024 / 1024
}
func memEnvLimits(log *zerolog.Logger) *envLimits {
mem := memMB()
k := 0
var recRAM uint64
for i, l := range defaults {
if mem >= l.RecommendedRAM && l.RecommendedRAM > recRAM {
k = i
recRAM = l.RecommendedRAM
}
}
if recRAM == 0 {
log.Warn().Uint64("memory_mb", mem).Msg("No settings with recommended ram found, using default.")
return defaultEnvLimits()
}
log.Info().Uint64("memory_mb", mem).Uint64("recommended_mb", recRAM).Msg("Found settings with recommended ram.")
return defaults[k]
}
func getMaxInt() int64 {
if strings.HasSuffix(runtime.GOARCH, "64") {
return math.MaxInt64
}
return math.MaxInt32
}