Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuang-Minh Nguyen <qmnguyen@gitlab.com>2023-11-22 11:32:08 +0300
committerQuang-Minh Nguyen <qmnguyen@gitlab.com>2023-11-22 11:32:08 +0300
commitb17bce5fcee8f38578568a7232e978b39633bb04 (patch)
treebcc948a7b2dc02ae8b03746b8f91622f3688f6f8
parent6835085898eb8d3881ee98c476a7bfc2981f0067 (diff)
cgroup: Allow a repository to use up to M repository cgroups
By default, a repository can spawn processes in at most one cgroups. If the number of repositories is more than the number of cgroups (likely), multiple repositories share the same one. This model works very well if the repositories under the management of Cgroup are equivalent in size or traffic. If a node has some enormous repositories (mono-repo, for example), the scoping cgroups become excessively large compared to the rest. This imbalance situation might force the operators to lift the repository-level cgroups. As a result, the isolation effect is not effective. This commit allows a repository to use up to M repository cgroups instead of one. This feature is designed to balance resource usage between cgroups, mitigate competition for resources within a single cgroup, and enhance memory usage efficiency and isolation. The value can be adjusted based on the specific workload and number of repository cgroups on the node. A Git process uses its target repository's relative path as the hash key to find the corresponding cgroup. It is allocated randomly to any of the consequent AllocationCount cgroups. It wraps around if needed.
-rw-r--r--internal/cgroups/handler_linux_test.go105
-rw-r--r--internal/cgroups/manager_linux.go29
-rw-r--r--internal/cgroups/manager_linux_test.go83
-rw-r--r--internal/cgroups/testhelper_test.go8
-rw-r--r--internal/gitaly/config/cgroups/cgroups.go21
-rw-r--r--internal/gitaly/config/cgroups/cgroups_test.go31
-rw-r--r--internal/gitaly/config/config.go4
-rw-r--r--internal/gitaly/config/config_test.go87
8 files changed, 281 insertions, 87 deletions
diff --git a/internal/cgroups/handler_linux_test.go b/internal/cgroups/handler_linux_test.go
index cb2a701af..bce5c5f09 100644
--- a/internal/cgroups/handler_linux_test.go
+++ b/internal/cgroups/handler_linux_test.go
@@ -241,8 +241,7 @@ func TestRepoCgroups(t *testing.T) {
// is creating repository directories in the correct location.
requireShards(t, version, mock, manager, pid)
- groupID := calcGroupID(cmdArgs, cfg.Repositories.Count)
-
+ groupID := uint(0) // Fixed - generated from the command
mock.setupMockCgroupFiles(t, manager, []uint{groupID})
require.False(t, manager.Ready())
@@ -286,33 +285,29 @@ func TestRepoCgroups(t *testing.T) {
func TestAddCommand(t *testing.T) {
for _, version := range []int{1, 2} {
t.Run("cgroups-v"+strconv.Itoa(version), func(t *testing.T) {
- mock := newMock(t, version)
-
- config := defaultCgroupsConfig()
- config.Repositories.Count = 10
- config.Repositories.MemoryBytes = 1024
- config.Repositories.CPUShares = 16
- config.HierarchyRoot = "gitaly"
- config.Mountpoint = mock.rootPath()
-
pid := 1
- manager1 := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
- mock.setupMockCgroupFiles(t, manager1, []uint{})
- require.NoError(t, manager1.Setup())
-
ctx := testhelper.Context(t)
- cmd2 := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
- require.NoError(t, cmd2.Run())
-
- groupID := calcGroupID(cmd2.Args, config.Repositories.Count)
+ t.Run("without overridden key", func(t *testing.T) {
+ mock := newMock(t, version)
+ config := defaultCgroupsConfig()
+ config.Repositories.Count = 10
+ config.Repositories.MemoryBytes = 1024
+ config.Repositories.CPUShares = 16
+ config.HierarchyRoot = "gitaly"
+ config.Mountpoint = mock.rootPath()
+
+ cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
+ require.NoError(t, cmd.Run())
- manager2 := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
+ manager := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
+ mock.setupMockCgroupFiles(t, manager, []uint{})
+ require.NoError(t, manager.Setup())
- t.Run("without overridden key", func(t *testing.T) {
- _, err := manager2.AddCommand(cmd2)
+ groupID := uint(8) // Fixed - generated from the command
+ _, err := manager.AddCommand(cmd)
require.NoError(t, err)
- requireShards(t, version, mock, manager2, pid, groupID)
+ requireShards(t, version, mock, manager, pid, groupID)
for _, path := range mock.repoPaths(pid, groupID) {
procsPath := filepath.Join(path, "cgroup.procs")
@@ -321,27 +316,79 @@ func TestAddCommand(t *testing.T) {
cmdPid, err := strconv.Atoi(string(content))
require.NoError(t, err)
- require.Equal(t, cmd2.Process.Pid, cmdPid)
+ require.Equal(t, cmd.Process.Pid, cmdPid)
}
})
t.Run("with overridden key", func(t *testing.T) {
- overriddenGroupID := calcGroupID([]string{"foobar"}, config.Repositories.Count)
+ mock := newMock(t, version)
+ config := defaultCgroupsConfig()
+ config.Repositories.Count = 10
+ config.Repositories.MemoryBytes = 1024
+ config.Repositories.CPUShares = 16
+ config.HierarchyRoot = "gitaly"
+ config.Mountpoint = mock.rootPath()
+
+ cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
+ require.NoError(t, cmd.Run())
+
+ manager := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
+ mock.setupMockCgroupFiles(t, manager, []uint{})
+ require.NoError(t, manager.Setup())
- _, err := manager2.AddCommand(cmd2, WithCgroupKey("foobar"))
+ groupID := uint(9) // Fixed - generated from the key
+ _, err := manager.AddCommand(cmd, WithCgroupKey("foobar"))
require.NoError(t, err)
- requireShards(t, version, mock, manager2, pid, groupID, overriddenGroupID)
+ requireShards(t, version, mock, manager, pid, groupID)
- for _, path := range mock.repoPaths(pid, overriddenGroupID) {
+ for _, path := range mock.repoPaths(pid, groupID) {
procsPath := filepath.Join(path, "cgroup.procs")
content := readCgroupFile(t, procsPath)
cmdPid, err := strconv.Atoi(string(content))
require.NoError(t, err)
- require.Equal(t, cmd2.Process.Pid, cmdPid)
+ require.Equal(t, cmd.Process.Pid, cmdPid)
}
})
+
+ t.Run("when AllocationSet is set", func(t *testing.T) {
+ mock := newMock(t, version)
+ config := defaultCgroupsConfig()
+ config.Repositories.Count = 10
+ config.Repositories.MaxCgroupsPerRepo = 3
+ config.Repositories.MemoryBytes = 1024
+ config.Repositories.CPUShares = 16
+ config.HierarchyRoot = "gitaly"
+ config.Mountpoint = mock.rootPath()
+
+ manager := mock.newCgroupManager(config, testhelper.SharedLogger(t), pid)
+ mock.setupMockCgroupFiles(t, manager, []uint{})
+ require.NoError(t, manager.Setup())
+
+ rand := newMockRand(t, 3)
+ manager.rand = rand
+
+ groupIDs := []uint{8, 9, 0}
+ for i := 0; i < 3; i++ {
+ cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
+ require.NoError(t, cmd.Run())
+ _, err := manager.AddCommand(cmd)
+ require.NoError(t, err)
+
+ for _, path := range mock.repoPaths(pid, groupIDs[i]) {
+ procsPath := filepath.Join(path, "cgroup.procs")
+ content := readCgroupFile(t, procsPath)
+
+ cmdPid, err := strconv.Atoi(string(content))
+ require.NoError(t, err)
+
+ require.Equal(t, cmd.Process.Pid, cmdPid)
+ }
+ }
+
+ requireShards(t, version, mock, manager, pid, groupIDs...)
+ })
})
}
}
diff --git a/internal/cgroups/manager_linux.go b/internal/cgroups/manager_linux.go
index 0ee67acc2..516b63fbc 100644
--- a/internal/cgroups/manager_linux.go
+++ b/internal/cgroups/manager_linux.go
@@ -7,6 +7,7 @@ import (
"fmt"
"hash/crc32"
"io"
+ "math/rand"
"os"
"os/exec"
"path/filepath"
@@ -14,6 +15,7 @@ import (
"sync"
"sync/atomic"
"syscall"
+ "time"
cgrps "github.com/containerd/cgroups/v3"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -72,6 +74,12 @@ func (s *cgroupStatus) getLock(cgroupPath string) *cgroupLock {
return cgLock
}
+// randomizer is the interface of the Go random number generator.
+type randomizer interface {
+ // Intn returns a random integer in the range [0,n).
+ Intn(n int) int
+}
+
// CGroupManager is a manager class that implements specific methods related to cgroups
type CGroupManager struct {
cfg cgroupscfg.Config
@@ -79,8 +87,8 @@ type CGroupManager struct {
enabled bool
repoRes *specs.LinuxResources
status *cgroupStatus
-
handler cgroupHandler
+ rand randomizer
}
func newCgroupManager(cfg cgroupscfg.Config, logger log.Logger, pid int) *CGroupManager {
@@ -106,6 +114,7 @@ func newCgroupManagerWithMode(cfg cgroupscfg.Config, logger log.Logger, pid int,
handler: handler,
repoRes: configRepositoryResources(cfg),
status: newCgroupStatus(cfg, handler.repoPath),
+ rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
@@ -199,13 +208,21 @@ func (cgm *CGroupManager) cgroupPathForCommand(cmd *exec.Cmd, opts []AddCommandO
if key == "" {
key = strings.Join(cmd.Args, "/")
}
+ groupID := cgm.calcGroupID(cgm.rand, key, cgm.cfg.Repositories.Count, cgm.cfg.Repositories.MaxCgroupsPerRepo)
+ return cgm.handler.repoPath(int(groupID))
+}
- checksum := crc32.ChecksumIEEE(
- []byte(key),
- )
+func (cgm *CGroupManager) calcGroupID(rand randomizer, key string, count uint, allocationCount uint) uint {
+ checksum := crc32.ChecksumIEEE([]byte(key))
- groupID := uint(checksum) % cgm.cfg.Repositories.Count
- return cgm.handler.repoPath(int(groupID))
+ // Pick a starting point
+ groupID := uint(checksum) % count
+ if allocationCount <= 1 {
+ return groupID
+ }
+
+ // Shift random distance [0, allocation) from the starting point. Wrap-around if needed.
+ return (groupID + uint(rand.Intn(int(allocationCount)))) % count
}
// Describe is used to generate description information for each CGroupManager prometheus metric
diff --git a/internal/cgroups/manager_linux_test.go b/internal/cgroups/manager_linux_test.go
index 3c9878169..30068edc4 100644
--- a/internal/cgroups/manager_linux_test.go
+++ b/internal/cgroups/manager_linux_test.go
@@ -14,25 +14,47 @@ import (
"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
)
+// mockRand is a mock for random number generator that implements randomizer interface of cgroups package.
+type mockRand struct {
+ t *testing.T
+ max int
+ n int
+}
+
+func newMockRand(t *testing.T, max int) *mockRand {
+ return &mockRand{t: t, max: max, n: -1}
+}
+
+// Intn returns 0 to inputMax-1 in turn then back to 0. It also verifies if the input argument matched the initialized max.
+func (r *mockRand) Intn(inputMax int) int {
+ require.Equalf(r.t, r.max, inputMax, "unexpected rand.Intn() argument, expected %d, actual %d", r.max, inputMax)
+
+ r.n++
+ if r.n >= r.max {
+ r.n = 0
+ }
+ return r.n
+}
+
func TestCloneIntoCgroup(t *testing.T) {
mountPoint := t.TempDir()
hierarchyRoot := filepath.Join(mountPoint, "gitaly")
// Create the files we expect the manager to open.
require.NoError(t, os.MkdirAll(filepath.Join(hierarchyRoot, "gitaly-1"), fs.ModePerm))
- require.NoError(t, os.MkdirAll(filepath.Join(hierarchyRoot, "gitaly-1", "repos-3"), fs.ModePerm))
- require.NoError(t, os.MkdirAll(filepath.Join(hierarchyRoot, "gitaly-1", "repos-5"), fs.ModePerm))
+ for i := 0; i < 10; i++ {
+ require.NoError(t, os.MkdirAll(filepath.Join(hierarchyRoot, "gitaly-1", fmt.Sprintf("repos-%d", i)), fs.ModePerm))
+ }
require.NoError(t, os.WriteFile(filepath.Join(hierarchyRoot, "gitaly-1", "cgroup.subtree_control"), nil, fs.ModePerm))
- mgr := NewManager(cgroups.Config{
- Mountpoint: mountPoint,
- HierarchyRoot: "gitaly",
- Repositories: cgroups.Repositories{
- Count: 10,
- },
- }, testhelper.NewLogger(t), 1)
-
t.Run("command args used as key", func(t *testing.T) {
+ mgr := NewManager(cgroups.Config{
+ Mountpoint: mountPoint,
+ HierarchyRoot: "gitaly",
+ Repositories: cgroups.Repositories{
+ Count: 10,
+ },
+ }, testhelper.NewLogger(t), 1)
for _, tc := range []struct {
desc string
existingSysProcAttr *syscall.SysProcAttr
@@ -74,6 +96,14 @@ func TestCloneIntoCgroup(t *testing.T) {
})
t.Run("key provided", func(t *testing.T) {
+ mgr := NewManager(cgroups.Config{
+ Mountpoint: mountPoint,
+ HierarchyRoot: "gitaly",
+ Repositories: cgroups.Repositories{
+ Count: 10,
+ },
+ }, testhelper.NewLogger(t), 1)
+
commandWithKey := exec.Command("command", "arg")
pathWithKey, closeWithKey, err := mgr.CloneIntoCgroup(commandWithKey, WithCgroupKey("some-key"))
require.NoError(t, err)
@@ -93,6 +123,39 @@ func TestCloneIntoCgroup(t *testing.T) {
require.NotEqual(t, pathWithKey, pathWithoutKey, "commands should be placed in different groups")
require.NotEqual(t, commandWithKey.SysProcAttr.CgroupFD, commandWithoutKey.SysProcAttr.CgroupFD)
})
+
+ t.Run("AllocationCount is set", func(t *testing.T) {
+ mgr := NewManager(cgroups.Config{
+ Mountpoint: mountPoint,
+ HierarchyRoot: "gitaly",
+ Repositories: cgroups.Repositories{
+ Count: 10,
+ MaxCgroupsPerRepo: 3,
+ },
+ }, testhelper.NewLogger(t), 1)
+
+ assertSpawnedCommand := func(key string, groupID string) {
+ commandWithKey := exec.Command("command", "arg")
+ pathWithKey, closeWithKey, err := mgr.CloneIntoCgroup(commandWithKey, WithCgroupKey(key))
+ require.NoError(t, err)
+ defer testhelper.MustClose(t, closeWithKey)
+ require.Equal(t, filepath.Join(hierarchyRoot, fmt.Sprintf("gitaly-1/%s", groupID)), pathWithKey)
+ require.True(t, commandWithKey.SysProcAttr.UseCgroupFD)
+ require.NotEqual(t, 0, commandWithKey.SysProcAttr.UseCgroupFD)
+ }
+
+ mgr.(*CGroupManager).rand = newMockRand(t, 3)
+ // Starting point is 3 (similar to the above test)
+ assertSpawnedCommand("some-key", "repos-3")
+ assertSpawnedCommand("some-key", "repos-4")
+ assertSpawnedCommand("some-key", "repos-5")
+
+ mgr.(*CGroupManager).rand = newMockRand(t, 3)
+ // Starting point is 8
+ assertSpawnedCommand("key-1", "repos-8")
+ assertSpawnedCommand("key-1", "repos-9")
+ assertSpawnedCommand("key-1", "repos-0") // Wrap-around
+ })
}
func TestNewCgroupStatus(t *testing.T) {
diff --git a/internal/cgroups/testhelper_test.go b/internal/cgroups/testhelper_test.go
index 25b7e008e..33f0b6b30 100644
--- a/internal/cgroups/testhelper_test.go
+++ b/internal/cgroups/testhelper_test.go
@@ -3,8 +3,6 @@
package cgroups
import (
- "hash/crc32"
- "strings"
"testing"
"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
@@ -16,9 +14,3 @@ var cmdArgs = []string{"ls", "-hal", "."}
func TestMain(m *testing.M) {
testhelper.Run(m)
}
-
-// calcGroupID calculates the repository cgroup ID for the key provided.
-func calcGroupID(key []string, ct uint) uint {
- checksum := crc32.ChecksumIEEE([]byte(strings.Join(key, "/")))
- return uint(checksum) % ct
-}
diff --git a/internal/gitaly/config/cgroups/cgroups.go b/internal/gitaly/config/cgroups/cgroups.go
index ff19650f9..b1a437dc5 100644
--- a/internal/gitaly/config/cgroups/cgroups.go
+++ b/internal/gitaly/config/cgroups/cgroups.go
@@ -61,6 +61,26 @@ type Repositories struct {
// Count is the number of cgroups that will be created for repository-level isolation
// of git commands.
Count uint `toml:"count"`
+ // MaxCgroupsPerRepo specifies the maximum number of cgroups to which a single repository can allocate its
+ // processes.
+ // By default, a repository can spawn processes in at most one cgroups. If the number of repositories
+ // is more than the number of cgroups (likely), multiple repositories share the same one. This model works very
+ // well if the repositories under the management of Cgroup are equivalent in size or traffic. If a node has some
+ // enormous repositories (mono-repo, for example), the scoping cgroups become excessively large comparing to the
+ // rest. This imbalance situation might force the operators to lift the repository-level cgroups. As a result,
+ // the isolation effect is not effective.
+ // This config is designed to balance resource usage between cgroups, mitigate competition for resources
+ // within a single cgroup, and enhance memory usage efficiency and isolation. The value can be adjusted based on
+ // the specific workload and number of repository cgroups on the node.
+ // A Git process uses its target repository's relative path as the hash key to find the corresponding cgroup. It
+ // is allocated randomly to any of the consequent MaxCgroupsPerRepo cgroups. It wraps around if needed.
+ // repo-X
+ // ┌───────┐
+ // □ □ □ □ □ □ □ ■ ■ ■ ■ ■ ■ ■ ■ □ □ □ □
+ // └────────┘
+ // repo-Y
+ // The default value is "1".
+ MaxCgroupsPerRepo uint `toml:"max_cgroups_per_repo"`
// MemoryBytes is the memory limit for each cgroup. 0 implies no memory limit.
MemoryBytes int64 `toml:"memory_bytes"`
// CPUShares are the shares of CPU that each cgroup is allowed to utilize. A value of 1024
@@ -77,6 +97,7 @@ type Repositories struct {
// Validate runs validation on all fields and compose all found errors.
func (r *Repositories) Validate(memBytes int64, cpuShares uint64, cpuQuotaUs int64) error {
return cfgerror.New().
+ Append(cfgerror.InRange(0, r.Count, r.MaxCgroupsPerRepo, cfgerror.InRangeOptIncludeMin, cfgerror.InRangeOptIncludeMax), "max_cgroups_per_repo").
Append(cfgerror.InRange(0, memBytes, r.MemoryBytes, cfgerror.InRangeOptIncludeMin, cfgerror.InRangeOptIncludeMax), "memory_bytes").
Append(cfgerror.InRange(0, cpuShares, r.CPUShares, cfgerror.InRangeOptIncludeMin, cfgerror.InRangeOptIncludeMax), "cpu_shares").
Append(cfgerror.InRange(0, cpuQuotaUs, r.CPUQuotaUs, cfgerror.InRangeOptIncludeMin, cfgerror.InRangeOptIncludeMax), "cpu_quota_us").
diff --git a/internal/gitaly/config/cgroups/cgroups_test.go b/internal/gitaly/config/cgroups/cgroups_test.go
index 979a027b4..5527415ea 100644
--- a/internal/gitaly/config/cgroups/cgroups_test.go
+++ b/internal/gitaly/config/cgroups/cgroups_test.go
@@ -168,9 +168,21 @@ func TestRepositories_Validate(t *testing.T) {
{
name: "valid",
repositories: Repositories{
- Count: 2,
- MemoryBytes: 1024,
- CPUShares: 16,
+ Count: 2,
+ MaxCgroupsPerRepo: 2,
+ MemoryBytes: 1024,
+ CPUShares: 16,
+ },
+ memBytes: 2048,
+ cpuShares: 32,
+ },
+ {
+ name: "valid",
+ repositories: Repositories{
+ Count: 2,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
+ CPUShares: 16,
},
memBytes: 2048,
cpuShares: 32,
@@ -178,16 +190,21 @@ func TestRepositories_Validate(t *testing.T) {
{
name: "invalid",
repositories: Repositories{
- Count: 2,
- MemoryBytes: 1024,
- CPUShares: 16,
- CPUQuotaUs: 32000,
+ Count: 2,
+ MaxCgroupsPerRepo: 3,
+ MemoryBytes: 1024,
+ CPUShares: 16,
+ CPUQuotaUs: 32000,
},
memBytes: 256,
cpuShares: 2,
cpuQuotaUs: 16000,
expectedErr: cfgerror.ValidationErrors{
cfgerror.NewValidationError(
+ fmt.Errorf("%w: 3 out of [0, 2]", cfgerror.ErrNotInRange),
+ "max_cgroups_per_repo",
+ ),
+ cfgerror.NewValidationError(
fmt.Errorf("%w: 1024 out of [0, 256]", cfgerror.ErrNotInRange),
"memory_bytes",
),
diff --git a/internal/gitaly/config/config.go b/internal/gitaly/config/config.go
index edf978996..49280cd3d 100644
--- a/internal/gitaly/config/config.go
+++ b/internal/gitaly/config/config.go
@@ -801,6 +801,10 @@ func (cfg *Cfg) SetDefaults() error {
cfg.Cgroups.FallbackToOldVersion()
+ if cfg.Cgroups.Repositories.Count != 0 && cfg.Cgroups.Repositories.MaxCgroupsPerRepo == 0 {
+ cfg.Cgroups.Repositories.MaxCgroupsPerRepo = 1
+ }
+
if cfg.Backup.Layout == "" {
cfg.Backup.Layout = "pointer"
}
diff --git a/internal/gitaly/config/config_test.go b/internal/gitaly/config/config_test.go
index 5e7d45689..417047951 100644
--- a/internal/gitaly/config/config_test.go
+++ b/internal/gitaly/config/config_test.go
@@ -1246,9 +1246,10 @@ func TestValidateCgroups(t *testing.T) {
Shares: 512,
},
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 1024,
- CPUShares: 512,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
+ CPUShares: 512,
},
},
},
@@ -1264,7 +1265,8 @@ func TestValidateCgroups(t *testing.T) {
Mountpoint: "/sys/fs/cgroup",
HierarchyRoot: "baz",
Repositories: cgroups.Repositories{
- Count: 10,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
},
},
},
@@ -1279,7 +1281,8 @@ func TestValidateCgroups(t *testing.T) {
Mountpoint: "/sys/fs/cgroup",
HierarchyRoot: "gitaly",
Repositories: cgroups.Repositories{
- Count: 10,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
},
},
},
@@ -1308,8 +1311,9 @@ func TestValidateCgroups(t *testing.T) {
Shares: 0,
},
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 1024,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
},
},
},
@@ -1339,8 +1343,9 @@ func TestValidateCgroups(t *testing.T) {
Shares: 512,
},
Repositories: cgroups.Repositories{
- Count: 10,
- CPUShares: 512,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ CPUShares: 512,
},
},
},
@@ -1385,10 +1390,11 @@ func TestValidateCgroups(t *testing.T) {
Mountpoint: "/sys/fs/cgroup",
HierarchyRoot: "gitaly",
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 1024,
- CPUShares: 512,
- CPUQuotaUs: 500,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
+ CPUShares: 512,
+ CPUQuotaUs: 500,
},
},
},
@@ -1427,9 +1433,10 @@ func TestValidateCgroups(t *testing.T) {
Mountpoint: "/sys/fs/cgroup",
HierarchyRoot: "gitaly",
Repositories: cgroups.Repositories{
- Count: 10,
- CPUShares: 512,
- CPUQuotaUs: 500,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ CPUShares: 512,
+ CPUQuotaUs: 500,
},
},
},
@@ -1454,10 +1461,11 @@ func TestValidateCgroups(t *testing.T) {
CPUShares: 1024,
CPUQuotaUs: 800,
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 2147483648,
- CPUShares: 128,
- CPUQuotaUs: 500,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 2147483648,
+ CPUShares: 128,
+ CPUQuotaUs: 500,
},
},
validateErr: errors.New("cgroups.repositories: memory limit cannot exceed parent"),
@@ -1477,8 +1485,9 @@ func TestValidateCgroups(t *testing.T) {
HierarchyRoot: "gitaly",
CPUShares: 128,
Repositories: cgroups.Repositories{
- Count: 10,
- CPUShares: 512,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ CPUShares: 512,
},
},
validateErr: errors.New("cgroups.repositories: cpu shares cannot exceed parent"),
@@ -1498,8 +1507,9 @@ func TestValidateCgroups(t *testing.T) {
HierarchyRoot: "gitaly",
CPUQuotaUs: 225,
Repositories: cgroups.Repositories{
- Count: 10,
- CPUQuotaUs: 500,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ CPUQuotaUs: 500,
},
},
validateErr: errors.New("cgroups.repositories: cpu quota cannot exceed parent"),
@@ -1520,9 +1530,32 @@ func TestValidateCgroups(t *testing.T) {
HierarchyRoot: "gitaly",
MetricsEnabled: true,
Repositories: cgroups.Repositories{
- Count: 10,
- MemoryBytes: 1024,
- CPUShares: 512,
+ Count: 10,
+ MaxCgroupsPerRepo: 1,
+ MemoryBytes: 1024,
+ CPUShares: 512,
+ },
+ },
+ },
+ {
+ name: "max_cgroups_per_repo enabled",
+ rawCfg: `[cgroups]
+ mountpoint = "/sys/fs/cgroup"
+ hierarchy_root = "gitaly"
+ [cgroups.repositories]
+ count = 10
+ max_cgroups_per_repo = 5
+ memory_bytes = 1024
+ cpu_shares = 512
+ `,
+ expect: cgroups.Config{
+ Mountpoint: "/sys/fs/cgroup",
+ HierarchyRoot: "gitaly",
+ Repositories: cgroups.Repositories{
+ Count: 10,
+ MaxCgroupsPerRepo: 5,
+ MemoryBytes: 1024,
+ CPUShares: 512,
},
},
},