Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions config/file_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (

"golang.org/x/exp/slices"
"gopkg.in/yaml.v3"

"github.com/honeycombio/refinery/internal/system"
)

// In order to be able to unmarshal "15s" etc. into time.Duration, we need to
Expand Down Expand Up @@ -544,6 +546,13 @@ func newFileConfig(opts *CmdEnv, cData, rulesData []configData, currentVersion .
return nil, err
}

// If AvailableMemory is not set, AND MaxAlloc is not set, try to detect it from the system
if mainconf.Collection.AvailableMemory == 0 && mainconf.Collection.MaxAlloc == 0 {
if mem, err := system.GetTotalMemory(); err == nil && mem > 0 {
allowed := uint64(float64(mem) * float64(mainconf.Collection.MaxMemoryPercentage) / 100.0)
mainconf.Collection.MaxAlloc = MemorySize(allowed)
}
}
var rulesconf *V2SamplerConfig
ruleshash, err := applyConfigInto(&rulesconf, rulesData, nil)
if err != nil {
Expand Down
8 changes: 4 additions & 4 deletions config/metadata/configMeta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1320,8 +1320,9 @@ groups:
description: >
This value will typically be set through an environment variable
controlled by the container or deploy script. If this value is zero or
not set, then `MaxMemoryPercentage` cannot be used to calculate the
maximum allocation and `MaxAlloc` will be used instead. If set, then
not set, Refinery will attempt to detect the total available memory from
the system (cgroups or /proc/meminfo). If detection fails, `MaxAlloc`
will be used. If set, then
this must be a memory size. Sizes with standard unit suffixes (such as
`MB` and `GiB`) and Kubernetes units (such as `M` and `Gi`) are
supported. Fractional values with a suffix are supported. If
Expand All @@ -1338,8 +1339,7 @@ groups:
validations:
- type: minimum
arg: 10
- type: requiredWith
arg: AvailableMemory

summary: is the maximum percentage of memory that should be allocated by the span collector.
description: >
If nonzero, then it must be an integer value between 1 and 100,
Expand Down
82 changes: 82 additions & 0 deletions internal/system/memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package system

import (
"bufio"
"bytes"
"errors"
"math"
"os"
"strconv"
"strings"
)

// GetTotalMemory returns the total available memory in bytes.
// It tries to read from cgroup limits first, then falls back to system memory.
func GetTotalMemory() (uint64, error) {
// Try cgroup v2
if mem, err := getCgroupV2Memory(); err == nil {
return mem, nil
}

// Try cgroup v1
if mem, err := getCgroupV1Memory(); err == nil {
return mem, nil
}

// Fall back to system memory
return getSystemMemory()
}

func getCgroupV2Memory() (uint64, error) {
data, err := os.ReadFile("/sys/fs/cgroup/memory.max")
if err != nil {
return 0, err
}
s := bytes.TrimSpace(data)
if bytes.Equal(s, []byte("max")) {
return 0, errors.New("no limit set in cgroup v2")
}
return strconv.ParseUint(string(s), 10, 64)
}

func getCgroupV1Memory() (uint64, error) {
data, err := os.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes")
if err != nil {
return 0, err
}
s := bytes.TrimSpace(data)
mem, err := strconv.ParseUint(string(s), 10, 64)
if err != nil {
return 0, err
}
// A very large number indicates no limit in cgroup v1
if mem > math.MaxInt64 {
return 0, errors.New("no limit set in cgroup v1")
}
return mem, nil
}

func getSystemMemory() (uint64, error) {
file, err := os.Open("/proc/meminfo")
if err != nil {
return 0, err
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "MemTotal:") {
parts := strings.Fields(line)
if len(parts) < 2 {
return 0, errors.New("invalid MemTotal format")
}
kb, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
return 0, err
}
return kb * 1024, nil
}
}
return 0, errors.New("MemTotal not found in /proc/meminfo")
}
3 changes: 2 additions & 1 deletion refinery_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,8 @@ Its minimum value should be at least three times the `CacheCapacity`.
`AvailableMemory` is the amount of system memory available to the Refinery process.

This value will typically be set through an environment variable controlled by the container or deploy script.
If this value is zero or not set, then `MaxMemoryPercentage` cannot be used to calculate the maximum allocation and `MaxAlloc` will be used instead.
If this value is zero or not set, Refinery will attempt to detect the total available memory from the system (cgroups or /proc/meminfo).
If detection fails, `MaxAlloc` will be used.
If set, then this must be a memory size.
Sizes with standard unit suffixes (such as `MB` and `GiB`) and Kubernetes units (such as `M` and `Gi`) are supported.
Fractional values with a suffix are supported.
Expand Down