Skip to content

Commit

Permalink
Percentage memory limiter settings (#1622)
Browse files Browse the repository at this point in the history
* Percentage memory limiter

Signed-off-by: Pavol Loffay <[email protected]>

* Some fixes

Signed-off-by: Pavol Loffay <[email protected]>

* Add OTEL license

Signed-off-by: Pavol Loffay <[email protected]>

* Add full OTEL license

Signed-off-by: Pavol Loffay <[email protected]>

* Fix review comments

Signed-off-by: Pavol Loffay <[email protected]>
  • Loading branch information
pavolloffay authored Sep 23, 2020
1 parent d62f440 commit 4f06a68
Show file tree
Hide file tree
Showing 34 changed files with 1,653 additions and 31 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ require (
github.com/jaegertracing/jaeger v1.19.2
github.com/joshdk/go-junit v0.0.0-20200702055522-6efcf4050909
github.com/jstemmer/go-junit-report v0.9.1
github.com/mitchellh/mapstructure v1.3.2
github.com/mjibson/esc v0.2.0
github.com/openzipkin/zipkin-go v0.2.4-0.20200818204336-dc18516bbb4c
github.com/orijtech/prometheus-go-metrics-exporter v0.0.5
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
Expand Down
21 changes: 21 additions & 0 deletions processor/memorylimiter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ allocated by the process heap. Note that typically the total memory usage of
process will be about 50MiB higher than this value.
- `spike_limit_mib` (default = 0): Maximum spike expected between the
measurements of memory usage. The value must be less than `limit_mib`.
- `limit_percentage` (default = 0): Maximum amount of total memory, in percents, targeted to be
allocated by the process heap. This configuration is supported on Linux systems with cgroups
and it's intended to be used in dynamic platforms like docker.
This option is used to calculate `memory_limit` from the total available memory.
For instance setting of 75% with the total memory of 1GiB will result in the limit of 750 MiB.
The fixed memory setting (`limit_mib`) takes precedence
over the percentage configuration.
- `spike_limit_percentage` (default = 0): Maximum spike expected between the
measurements of memory usage. The value must be less than `limit_percentage`.
This option is used to calculate `spike_limit_mib` from the total available memory.
For instance setting of 25% with the total memory of 1GiB will result in the spike limit of 250MiB.
This option is intended to be used only with `limit_percentage`.

The following configuration options can also be modified:
- `ballast_size_mib` (default = 0): Must match the `mem-ballast-size-mib`
Expand All @@ -62,5 +74,14 @@ processors:
spike_limit_mib: 500
```
```yaml
processors:
memory_limiter:
ballast_size_mib: 2000
check_interval: 5s
limit_percentage: 50
spike_limit_percentage: 30
```
Refer to [config.yaml](./testdata/config.yaml) for detailed
examples on using the processor.
7 changes: 7 additions & 0 deletions processor/memorylimiter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ type Config struct {
// BallastSizeMiB is the size, in MiB, of the ballast size being used by the
// process.
BallastSizeMiB uint32 `mapstructure:"ballast_size_mib"`

// MemoryLimitPercentage is the maximum amount of memory, in %, targeted to be
// allocated by the process. The fixed memory settings MemoryLimitMiB has a higher precedence.
MemoryLimitPercentage uint32 `mapstructure:"limit_percentage"`
// MemorySpikePercentage is the maximum, in percents against the total memory,
// spike expected between the measurements of memory usage.
MemorySpikePercentage uint32 `mapstructure:"spike_limit_percentage"`
}

// Name of BallastSizeMiB config option.
Expand Down
94 changes: 94 additions & 0 deletions processor/memorylimiter/internal/cgroups/cgroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Keep the the original Uber license.

// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// +build linux

package cgroups

import (
"bufio"
"io"
"os"
"path/filepath"
"strconv"
)

// CGroup represents the data structure for a Linux control group.
type CGroup struct {
path string
}

// NewCGroup returns a new *CGroup from a given path.
func NewCGroup(path string) *CGroup {
return &CGroup{path: path}
}

// Path returns the path of the CGroup*.
func (cg *CGroup) Path() string {
return cg.path
}

// ParamPath returns the path of the given cgroup param under itself.
func (cg *CGroup) ParamPath(param string) string {
return filepath.Join(cg.path, param)
}

// readFirstLine reads the first line from a cgroup param file.
func (cg *CGroup) readFirstLine(param string) (string, error) {
paramFile, err := os.Open(cg.ParamPath(param))
if err != nil {
return "", err
}
defer paramFile.Close()

scanner := bufio.NewScanner(paramFile)
if scanner.Scan() {
return scanner.Text(), nil
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", io.ErrUnexpectedEOF
}

// readInt parses the first line from a cgroup param file as int.
func (cg *CGroup) readInt(param string) (int, error) {
text, err := cg.readFirstLine(param)
if err != nil {
return 0, err
}
return strconv.Atoi(text)
}
153 changes: 153 additions & 0 deletions processor/memorylimiter/internal/cgroups/cgroup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Keep the the original Uber license.

// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// +build linux

package cgroups

import (
"fmt"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCGroupParamPath(t *testing.T) {
cgroup := NewCGroup("/sys/fs/cgroup/cpu")
assert.Equal(t, "/sys/fs/cgroup/cpu", cgroup.Path())
assert.Equal(t, "/sys/fs/cgroup/cpu/cpu.cfs_quota_us", cgroup.ParamPath("cpu.cfs_quota_us"))
}

func TestCGroupReadFirstLine(t *testing.T) {
testTable := []struct {
name string
paramName string
expectedContent string
shouldHaveError bool
}{
{
name: "cpu",
paramName: "cpu.cfs_period_us",
expectedContent: "100000",
shouldHaveError: false,
},
{
name: "absent",
paramName: "cpu.stat",
expectedContent: "",
shouldHaveError: true,
},
{
name: "empty",
paramName: "cpu.cfs_quota_us",
expectedContent: "",
shouldHaveError: true,
},
}

for _, tt := range testTable {
cgroupPath := filepath.Join(testDataCGroupsPath, tt.name)
cgroup := NewCGroup(cgroupPath)

content, err := cgroup.readFirstLine(tt.paramName)
assert.Equal(t, tt.expectedContent, content, tt.name)

if tt.shouldHaveError {
assert.Error(t, err, tt.name)
} else {
assert.NoError(t, err, tt.name)
}
}
}

func TestCGroupReadInt(t *testing.T) {
testTable := []struct {
name string
paramName string
expectedValue int
shouldHaveError bool
}{
{
name: "cpu",
paramName: "cpu.cfs_period_us",
expectedValue: 100000,
shouldHaveError: false,
},
{
name: "empty",
paramName: "cpu.cfs_quota_us",
expectedValue: 0,
shouldHaveError: true,
},
{
name: "invalid",
paramName: "cpu.cfs_quota_us",
expectedValue: 0,
shouldHaveError: true,
},
{
name: "absent",
paramName: "cpu.cfs_quota_us",
expectedValue: 0,
shouldHaveError: true,
},
}

for _, tt := range testTable {
cgroupPath := filepath.Join(testDataCGroupsPath, tt.name)
cgroup := NewCGroup(cgroupPath)

value, err := cgroup.readInt(tt.paramName)
assert.Equal(t, tt.expectedValue, value, "%s/%s", tt.name, tt.paramName)

if tt.shouldHaveError {
assert.Error(t, err, tt.name)
} else {
assert.NoError(t, err, tt.name)
}
}
}

func TestCGroupMemory(t *testing.T) {
process, err := NewCGroupsForCurrentProcess()
require.NoError(t, err)
quota, b, err := process.MemoryQuota()
require.True(t, b)
require.NoError(t, err)
fmt.Println(quota)
}
Loading

0 comments on commit 4f06a68

Please sign in to comment.