build(deps): bump github.com/KimMachineGun/automemlimit
Bumps [github.com/KimMachineGun/automemlimit](https://github.com/KimMachineGun/automemlimit) from 0.4.0 to 0.5.0. - [Commits](https://github.com/KimMachineGun/automemlimit/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: github.com/KimMachineGun/automemlimit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Ralf Haferkamp
parent
3cf6543899
commit
8ed9b1a727
@@ -4,7 +4,7 @@ go 1.21
|
||||
|
||||
require (
|
||||
github.com/CiscoM31/godata v1.0.9
|
||||
github.com/KimMachineGun/automemlimit v0.4.0
|
||||
github.com/KimMachineGun/automemlimit v0.5.0
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/MicahParks/keyfunc v1.9.0
|
||||
github.com/Nerzal/gocloak/v13 v13.8.0
|
||||
@@ -284,6 +284,7 @@ require (
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/cachecontrol v0.1.0 // indirect
|
||||
|
||||
@@ -799,8 +799,8 @@ github.com/CiscoM31/godata v1.0.9 h1:7ovi2efjWb6RloX96AJqBB9eyIfBdarzj8kzg7glPC4
|
||||
github.com/CiscoM31/godata v1.0.9/go.mod h1:ZMiT6JuD3Rm83HEtiTx4JEChsd25YCrxchKGag/sdTc=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
|
||||
github.com/KimMachineGun/automemlimit v0.4.0 h1:qOjSDbAUENEL6fiKmRKuAVhPaLijpoEHFDTE+I+prp0=
|
||||
github.com/KimMachineGun/automemlimit v0.4.0/go.mod h1:pJhTW/nWJMj6SnWSU2TEKSlCaM+1N5Mej+IfS/5/Ol0=
|
||||
github.com/KimMachineGun/automemlimit v0.5.0 h1:BeOe+BbJc8L5chL3OwzVYjVzyvPALdd5wxVVOWuUZmQ=
|
||||
github.com/KimMachineGun/automemlimit v0.5.0/go.mod h1:di3GCKiu9Y+1fs92erCbUvKzPkNyViN3mA0vti/ykEQ=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
@@ -1799,6 +1799,8 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
|
||||
|
||||
+17
-1
@@ -8,6 +8,14 @@ Automatically set `GOMEMLIMIT` to match Linux [cgroups(7)](https://man7.org/linu
|
||||
|
||||
See more details about `GOMEMLIMIT` [here](https://tip.golang.org/doc/gc-guide#Memory_limit).
|
||||
|
||||
## Notice
|
||||
|
||||
Version `v0.5.0` introduces a fallback to system memory limits as an experimental feature when cgroup limits are unavailable. Activate this by setting `AUTOMEMLIMIT_EXPERIMENT=system`.
|
||||
You can also use system memory limits via `memlimit.FromSystem` provider directly.
|
||||
|
||||
This feature is under evaluation and might become a default or be removed based on user feedback.
|
||||
If you have any feedback about this feature, please open an issue.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
@@ -34,9 +42,17 @@ import "github.com/KimMachineGun/automemlimit/memlimit"
|
||||
func init() {
|
||||
memlimit.SetGoMemLimitWithOpts(
|
||||
memlimit.WithRatio(0.9),
|
||||
memlimit.WithEnv(),
|
||||
memlimit.WithProvider(memlimit.FromCgroup),
|
||||
)
|
||||
memlimit.SetGoMemLimitWithOpts(
|
||||
memlimit.WithRatio(0.9),
|
||||
memlimit.WithProvider(
|
||||
memlimit.ApplyFallback(
|
||||
memlimit.FromCgroup,
|
||||
memlimit.FromSystem,
|
||||
),
|
||||
),
|
||||
)
|
||||
memlimit.SetGoMemLimitWithEnv()
|
||||
memlimit.SetGoMemLimit(0.9)
|
||||
memlimit.SetGoMemLimitWithProvider(memlimit.Limit(1024*1024), 0.9)
|
||||
|
||||
+6
-85
@@ -1,91 +1,12 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package memlimit
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/cgroups/v3"
|
||||
"github.com/containerd/cgroups/v3/cgroup1"
|
||||
"github.com/containerd/cgroups/v3/cgroup2"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
cgroupMountPoint = "/sys/fs/cgroup"
|
||||
var (
|
||||
// ErrNoCgroup is returned when the process is not in cgroup.
|
||||
ErrNoCgroup = errors.New("process is not in cgroup")
|
||||
// ErrCgroupsNotSupported is returned when the system does not support cgroups.
|
||||
ErrCgroupsNotSupported = errors.New("cgroups is not supported on this system")
|
||||
)
|
||||
|
||||
// FromCgroup returns the memory limit based on the cgroups version on this system.
|
||||
func FromCgroup() (uint64, error) {
|
||||
switch cgroups.Mode() {
|
||||
case cgroups.Legacy:
|
||||
return FromCgroupV1()
|
||||
case cgroups.Hybrid:
|
||||
return FromCgroupHybrid()
|
||||
case cgroups.Unified:
|
||||
return FromCgroupV2()
|
||||
}
|
||||
return 0, ErrNoCgroup
|
||||
}
|
||||
|
||||
// FromCgroupV1 returns the memory limit from the cgroup v1.
|
||||
func FromCgroupV1() (uint64, error) {
|
||||
cg, err := cgroup1.Load(cgroup1.RootPath, cgroup1.WithHiearchy(
|
||||
cgroup1.SingleSubsystem(cgroup1.Default, cgroup1.Memory),
|
||||
))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
metrics, err := cg.Stat(cgroup1.IgnoreNotExist)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if limit := metrics.GetMemory().GetHierarchicalMemoryLimit(); limit != 0 {
|
||||
return limit, nil
|
||||
}
|
||||
|
||||
return 0, ErrNoLimit
|
||||
}
|
||||
|
||||
// FromCgroupHybrid returns the memory limit from the cgroup v1 or v2.
|
||||
// It checks the cgroup v2 first, and if it fails, it falls back to cgroup v1.
|
||||
func FromCgroupHybrid() (uint64, error) {
|
||||
limit, err := fromCgroupV2(filepath.Join(cgroupMountPoint, "unified"))
|
||||
if err == nil {
|
||||
return limit, nil
|
||||
} else if err != ErrNoLimit {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return FromCgroupV1()
|
||||
}
|
||||
|
||||
// FromCgroupV2 returns the memory limit from the cgroup v2.
|
||||
func FromCgroupV2() (uint64, error) {
|
||||
return fromCgroupV2(cgroupMountPoint)
|
||||
}
|
||||
|
||||
func fromCgroupV2(mountPoint string) (uint64, error) {
|
||||
path, err := cgroup2.NestedGroupPath("")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
m, err := cgroup2.Load(path, cgroup2.WithMountpoint(mountPoint))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
stats, err := m.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if limit := stats.GetMemory().GetUsageLimit(); limit != 0 {
|
||||
return limit, nil
|
||||
}
|
||||
|
||||
return 0, ErrNoLimit
|
||||
}
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package memlimit
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/cgroups/v3"
|
||||
"github.com/containerd/cgroups/v3/cgroup1"
|
||||
"github.com/containerd/cgroups/v3/cgroup2"
|
||||
)
|
||||
|
||||
const (
|
||||
cgroupMountPoint = "/sys/fs/cgroup"
|
||||
)
|
||||
|
||||
// FromCgroup returns the memory limit based on the cgroups version on this system.
|
||||
func FromCgroup() (uint64, error) {
|
||||
switch cgroups.Mode() {
|
||||
case cgroups.Legacy:
|
||||
return FromCgroupV1()
|
||||
case cgroups.Hybrid:
|
||||
return FromCgroupHybrid()
|
||||
case cgroups.Unified:
|
||||
return FromCgroupV2()
|
||||
}
|
||||
return 0, ErrNoCgroup
|
||||
}
|
||||
|
||||
// FromCgroupV1 returns the memory limit from the cgroup v1.
|
||||
func FromCgroupV1() (uint64, error) {
|
||||
cg, err := cgroup1.Load(cgroup1.RootPath, cgroup1.WithHiearchy(
|
||||
cgroup1.SingleSubsystem(cgroup1.Default, cgroup1.Memory),
|
||||
))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
metrics, err := cg.Stat(cgroup1.IgnoreNotExist)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if limit := metrics.GetMemory().GetHierarchicalMemoryLimit(); limit != 0 && limit != getCgroupV1NoLimit() {
|
||||
return limit, nil
|
||||
}
|
||||
|
||||
return 0, ErrNoLimit
|
||||
}
|
||||
|
||||
func getCgroupV1NoLimit() uint64 {
|
||||
ps := uint64(os.Getpagesize())
|
||||
return math.MaxInt64 / ps * ps
|
||||
}
|
||||
|
||||
// FromCgroupHybrid returns the memory limit from the cgroup v1 or v2.
|
||||
// It checks the cgroup v2 first, and if it fails, it falls back to cgroup v1.
|
||||
func FromCgroupHybrid() (uint64, error) {
|
||||
limit, err := fromCgroupV2(filepath.Join(cgroupMountPoint, "unified"))
|
||||
if err == nil {
|
||||
return limit, nil
|
||||
} else if err != ErrNoLimit {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return FromCgroupV1()
|
||||
}
|
||||
|
||||
// FromCgroupV2 returns the memory limit from the cgroup v2.
|
||||
func FromCgroupV2() (uint64, error) {
|
||||
return fromCgroupV2(cgroupMountPoint)
|
||||
}
|
||||
|
||||
func fromCgroupV2(mountPoint string) (uint64, error) {
|
||||
path, err := cgroup2.NestedGroupPath("")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
m, err := cgroup2.Load(path, cgroup2.WithMountpoint(mountPoint))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
stats, err := m.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if limit := stats.GetMemory().GetUsageLimit(); limit != 0 && limit != math.MaxUint64 {
|
||||
return limit, nil
|
||||
}
|
||||
|
||||
return 0, ErrNoLimit
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package memlimit
|
||||
|
||||
import (
|
||||
"github.com/pbnjay/memory"
|
||||
)
|
||||
|
||||
// FromSystem returns the total memory of the system.
|
||||
func FromSystem() (uint64, error) {
|
||||
limit := memory.TotalMemory()
|
||||
if limit == 0 {
|
||||
return 0, ErrNoLimit
|
||||
}
|
||||
return limit, nil
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package memlimit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
envAUTOMEMLIMIT_EXPERIMENT = "AUTOMEMLIMIT_EXPERIMENT"
|
||||
)
|
||||
|
||||
// Experiments is a set of experiment flags.
|
||||
// It is used to enable experimental features.
|
||||
//
|
||||
// You can set the flags by setting the environment variable AUTOMEMLIMIT_EXPERIMENT.
|
||||
// The value of the environment variable is a comma-separated list of experiment names.
|
||||
//
|
||||
// The following experiment names are known:
|
||||
//
|
||||
// - none: disable all experiments
|
||||
// - system: enable fallback to system memory limit
|
||||
type Experiments struct {
|
||||
// System enables fallback to system memory limit.
|
||||
System bool
|
||||
}
|
||||
|
||||
func parseExperiments() (Experiments, error) {
|
||||
var exp Experiments
|
||||
|
||||
// Create a map of known experiment names.
|
||||
names := make(map[string]func(bool))
|
||||
rv := reflect.ValueOf(&exp).Elem()
|
||||
rt := rv.Type()
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
field := rv.Field(i)
|
||||
names[strings.ToLower(rt.Field(i).Name)] = field.SetBool
|
||||
}
|
||||
|
||||
// Parse names.
|
||||
for _, f := range strings.Split(os.Getenv(envAUTOMEMLIMIT_EXPERIMENT), ",") {
|
||||
if f == "" {
|
||||
continue
|
||||
}
|
||||
if f == "none" {
|
||||
exp = Experiments{}
|
||||
continue
|
||||
}
|
||||
val := true
|
||||
set, ok := names[f]
|
||||
if !ok {
|
||||
return Experiments{}, fmt.Errorf("unknown AUTOMEMLIMIT_EXPERIMENT %s", f)
|
||||
}
|
||||
set(val)
|
||||
}
|
||||
|
||||
return exp, nil
|
||||
}
|
||||
+27
-18
@@ -22,16 +22,11 @@ const (
|
||||
var (
|
||||
// ErrNoLimit is returned when the memory limit is not set.
|
||||
ErrNoLimit = errors.New("memory is not limited")
|
||||
// ErrNoCgroup is returned when the process is not in cgroup.
|
||||
ErrNoCgroup = errors.New("process is not in cgroup")
|
||||
// ErrCgroupsNotSupported is returned when the system does not support cgroups.
|
||||
ErrCgroupsNotSupported = errors.New("cgroups is not supported on this system")
|
||||
)
|
||||
|
||||
type config struct {
|
||||
logger *log.Logger
|
||||
ratio float64
|
||||
env bool
|
||||
provider Provider
|
||||
}
|
||||
|
||||
@@ -50,10 +45,10 @@ func WithRatio(ratio float64) Option {
|
||||
// WithEnv configures whether to use environment variables.
|
||||
//
|
||||
// Default: false
|
||||
//
|
||||
// Deprecated: currently this does nothing.
|
||||
func WithEnv() Option {
|
||||
return func(cfg *config) {
|
||||
cfg.env = true
|
||||
}
|
||||
return func(cfg *config) {}
|
||||
}
|
||||
|
||||
// WithProvider configures the provider.
|
||||
@@ -65,17 +60,24 @@ func WithProvider(provider Provider) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// SetGoMemLimitWithOpts sets GOMEMLIMIT with options.
|
||||
// SetGoMemLimitWithOpts sets GOMEMLIMIT with options and environment variables.
|
||||
//
|
||||
// You can configure how much memory of the cgroup's memory limit to set as GOMEMLIMIT
|
||||
// through AUTOMEMLIMIT envrironment variable in the half-open range (0.0,1.0].
|
||||
//
|
||||
// If AUTOMEMLIMIT is not set, it defaults to 0.9. (10% is the headroom for memory sources the Go runtime is unaware of.)
|
||||
// If GOMEMLIMIT is already set or AUTOMEMLIMIT=off, this function does nothing.
|
||||
//
|
||||
// If AUTOMEMLIMIT_EXPERIMENT is set, it enables experimental features.
|
||||
// Please see the documentation of Experiments for more details.
|
||||
//
|
||||
// Options:
|
||||
// - WithRatio
|
||||
// - WithEnv (see more SetGoMemLimitWithEnv)
|
||||
// - WithProvider
|
||||
func SetGoMemLimitWithOpts(opts ...Option) (_ int64, _err error) {
|
||||
cfg := &config{
|
||||
logger: log.New(io.Discard, "", log.LstdFlags),
|
||||
ratio: defaultAUTOMEMLIMIT,
|
||||
env: false,
|
||||
provider: FromCgroup,
|
||||
}
|
||||
if os.Getenv(envAUTOMEMLIMIT_DEBUG) == "true" {
|
||||
@@ -90,6 +92,15 @@ func SetGoMemLimitWithOpts(opts ...Option) (_ int64, _err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
exps, err := parseExperiments()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to parse experiments: %w", err)
|
||||
}
|
||||
if exps.System {
|
||||
cfg.logger.Println("system experiment is enabled: using system memory limit as a fallback")
|
||||
cfg.provider = ApplyFallback(cfg.provider, FromSystem)
|
||||
}
|
||||
|
||||
snapshot := debug.SetMemoryLimit(-1)
|
||||
defer func() {
|
||||
err := recover()
|
||||
@@ -122,6 +133,10 @@ func SetGoMemLimitWithOpts(opts ...Option) (_ int64, _err error) {
|
||||
|
||||
limit, err := setGoMemLimit(ApplyRatio(cfg.provider, ratio))
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNoLimit) {
|
||||
cfg.logger.Printf("memory is not limited, skipping: %v\n", err)
|
||||
return 0, nil
|
||||
}
|
||||
return 0, fmt.Errorf("failed to set GOMEMLIMIT: %w", err)
|
||||
}
|
||||
|
||||
@@ -130,14 +145,8 @@ func SetGoMemLimitWithOpts(opts ...Option) (_ int64, _err error) {
|
||||
return limit, nil
|
||||
}
|
||||
|
||||
// SetGoMemLimitWithEnv sets GOMEMLIMIT with the value from the environment variable.
|
||||
// You can configure how much memory of the cgroup's memory limit to set as GOMEMLIMIT
|
||||
// through AUTOMEMLIMIT in the half-open range (0.0,1.0].
|
||||
//
|
||||
// If AUTOMEMLIMIT is not set, it defaults to 0.9. (10% is the headroom for memory sources the Go runtime is unaware of.)
|
||||
// If GOMEMLIMIT is already set or AUTOMEMLIMIT=off, this function does nothing.
|
||||
func SetGoMemLimitWithEnv() {
|
||||
_, _ = SetGoMemLimitWithOpts(WithEnv())
|
||||
_, _ = SetGoMemLimitWithOpts()
|
||||
}
|
||||
|
||||
// SetGoMemLimit sets GOMEMLIMIT with the value from the cgroup's memory limit and given ratio.
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017, Jeremy Jay
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# memory
|
||||
|
||||
Package `memory` provides two methods reporting total physical system memory
|
||||
accessible to the kernel, and free memory available to the running application.
|
||||
|
||||
This package has no external dependency besides the standard library and default operating system tools.
|
||||
|
||||
Documentation:
|
||||
[](https://godoc.org/github.com/pbnjay/memory)
|
||||
|
||||
This is useful for dynamic code to minimize thrashing and other contention, similar to the stdlib `runtime.NumCPU`
|
||||
See some history of the proposal at https://github.com/golang/go/issues/21816
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
fmt.Printf("Total system memory: %d\n", memory.TotalMemory())
|
||||
fmt.Printf("Free memory: %d\n", memory.FreeMemory())
|
||||
```
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
Tested/working on:
|
||||
- macOS 10.12.6 (16G29), 10.15.7 (19H2)
|
||||
- Windows 10 1511 (10586.1045)
|
||||
- Linux RHEL (3.10.0-327.3.1.el7.x86_64)
|
||||
- Raspberry Pi 3 (ARMv8) on Raspbian, ODROID-C1+ (ARMv7) on Ubuntu, C.H.I.P
|
||||
(ARMv7).
|
||||
- Amazon Linux 2 aarch64 (m6a.large, 4.14.203-156.332.amzn2.aarch64)
|
||||
|
||||
Tested on virtual machines:
|
||||
- Windows 7 SP1 386
|
||||
- Debian stretch 386
|
||||
- NetBSD 7.1 amd64 + 386
|
||||
- OpenBSD 6.1 amd64 + 386
|
||||
- FreeBSD 11.1 amd64 + 386
|
||||
- DragonFly BSD 4.8.1 amd64
|
||||
|
||||
If you have access to untested systems please test and report success/bugs.
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Package memory provides a single method reporting total system memory
|
||||
// accessible to the kernel.
|
||||
package memory
|
||||
|
||||
// TotalMemory returns the total accessible system memory in bytes.
|
||||
//
|
||||
// The total accessible memory is installed physical memory size minus reserved
|
||||
// areas for the kernel and hardware, if such reservations are reported by
|
||||
// the operating system.
|
||||
//
|
||||
// If accessible memory size could not be determined, then 0 is returned.
|
||||
func TotalMemory() uint64 {
|
||||
return sysTotalMemory()
|
||||
}
|
||||
|
||||
// FreeMemory returns the total free system memory in bytes.
|
||||
//
|
||||
// The total free memory is installed physical memory size minus reserved
|
||||
// areas for other applications running on the same system.
|
||||
//
|
||||
// If free memory size could not be determined, then 0 is returned.
|
||||
func FreeMemory() uint64 {
|
||||
return sysFreeMemory()
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// +build freebsd openbsd dragonfly netbsd
|
||||
|
||||
package memory
|
||||
|
||||
func sysTotalMemory() uint64 {
|
||||
s, err := sysctlUint64("hw.physmem")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func sysFreeMemory() uint64 {
|
||||
s, err := sysctlUint64("hw.usermem")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return s
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// +build darwin
|
||||
|
||||
package memory
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func sysTotalMemory() uint64 {
|
||||
s, err := sysctlUint64("hw.memsize")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func sysFreeMemory() uint64 {
|
||||
cmd := exec.Command("vm_stat")
|
||||
outBytes, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
rePageSize := regexp.MustCompile("page size of ([0-9]*) bytes")
|
||||
reFreePages := regexp.MustCompile("Pages free: *([0-9]*)\\.")
|
||||
|
||||
// default: page size of 4096 bytes
|
||||
matches := rePageSize.FindSubmatchIndex(outBytes)
|
||||
pageSize := uint64(4096)
|
||||
if len(matches) == 4 {
|
||||
pageSize, err = strconv.ParseUint(string(outBytes[matches[2]:matches[3]]), 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// ex: Pages free: 1126961.
|
||||
matches = reFreePages.FindSubmatchIndex(outBytes)
|
||||
freePages := uint64(0)
|
||||
if len(matches) == 4 {
|
||||
freePages, err = strconv.ParseUint(string(outBytes[matches[2]:matches[3]]), 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return freePages * pageSize
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// +build linux
|
||||
|
||||
package memory
|
||||
|
||||
import "syscall"
|
||||
|
||||
func sysTotalMemory() uint64 {
|
||||
in := &syscall.Sysinfo_t{}
|
||||
err := syscall.Sysinfo(in)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
// If this is a 32-bit system, then these fields are
|
||||
// uint32 instead of uint64.
|
||||
// So we always convert to uint64 to match signature.
|
||||
return uint64(in.Totalram) * uint64(in.Unit)
|
||||
}
|
||||
|
||||
func sysFreeMemory() uint64 {
|
||||
in := &syscall.Sysinfo_t{}
|
||||
err := syscall.Sysinfo(in)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
// If this is a 32-bit system, then these fields are
|
||||
// uint32 instead of uint64.
|
||||
// So we always convert to uint64 to match signature.
|
||||
return uint64(in.Freeram) * uint64(in.Unit)
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// +build windows
|
||||
|
||||
package memory
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// omitting a few fields for brevity...
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366589(v=vs.85).aspx
|
||||
type memStatusEx struct {
|
||||
dwLength uint32
|
||||
dwMemoryLoad uint32
|
||||
ullTotalPhys uint64
|
||||
ullAvailPhys uint64
|
||||
unused [5]uint64
|
||||
}
|
||||
|
||||
func sysTotalMemory() uint64 {
|
||||
kernel32, err := syscall.LoadDLL("kernel32.dll")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
// GetPhysicallyInstalledSystemMemory is simpler, but broken on
|
||||
// older versions of windows (and uses this under the hood anyway).
|
||||
globalMemoryStatusEx, err := kernel32.FindProc("GlobalMemoryStatusEx")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
msx := &memStatusEx{
|
||||
dwLength: 64,
|
||||
}
|
||||
r, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx)))
|
||||
if r == 0 {
|
||||
return 0
|
||||
}
|
||||
return msx.ullTotalPhys
|
||||
}
|
||||
|
||||
func sysFreeMemory() uint64 {
|
||||
kernel32, err := syscall.LoadDLL("kernel32.dll")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
// GetPhysicallyInstalledSystemMemory is simpler, but broken on
|
||||
// older versions of windows (and uses this under the hood anyway).
|
||||
globalMemoryStatusEx, err := kernel32.FindProc("GlobalMemoryStatusEx")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
msx := &memStatusEx{
|
||||
dwLength: 64,
|
||||
}
|
||||
r, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx)))
|
||||
if r == 0 {
|
||||
return 0
|
||||
}
|
||||
return msx.ullAvailPhys
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// +build darwin freebsd openbsd dragonfly netbsd
|
||||
|
||||
package memory
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func sysctlUint64(name string) (uint64, error) {
|
||||
s, err := syscall.Sysctl(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// hack because the string conversion above drops a \0
|
||||
b := []byte(s)
|
||||
if len(b) < 8 {
|
||||
b = append(b, 0)
|
||||
}
|
||||
return *(*uint64)(unsafe.Pointer(&b[0])), nil
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// +build !linux,!darwin,!windows,!freebsd,!dragonfly,!netbsd,!openbsd
|
||||
|
||||
package memory
|
||||
|
||||
func sysTotalMemory() uint64 {
|
||||
return 0
|
||||
}
|
||||
func sysFreeMemory() uint64 {
|
||||
return 0
|
||||
}
|
||||
Vendored
+4
-1
@@ -14,7 +14,7 @@ github.com/BurntSushi/toml/internal
|
||||
# github.com/CiscoM31/godata v1.0.9
|
||||
## explicit; go 1.19
|
||||
github.com/CiscoM31/godata
|
||||
# github.com/KimMachineGun/automemlimit v0.4.0
|
||||
# github.com/KimMachineGun/automemlimit v0.5.0
|
||||
## explicit; go 1.19
|
||||
github.com/KimMachineGun/automemlimit
|
||||
github.com/KimMachineGun/automemlimit/memlimit
|
||||
@@ -1599,6 +1599,9 @@ github.com/oxtoacart/bpool
|
||||
# github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
## explicit
|
||||
github.com/patrickmn/go-cache
|
||||
# github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||
## explicit; go 1.16
|
||||
github.com/pbnjay/memory
|
||||
# github.com/pjbgf/sha1cd v0.3.0
|
||||
## explicit; go 1.19
|
||||
github.com/pjbgf/sha1cd
|
||||
|
||||
Reference in New Issue
Block a user